@fitzzero/quickdraw-core 5.1.1 → 5.1.3

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 (152) hide show
  1. package/README.md +1308 -219
  2. package/dist/{admin-DKwkIh5i.d.ts → admin-CS7JB_UH.d.ts} +2 -2
  3. package/dist/{adminSchemas-2DUA_hZ8.d.ts → adminSchemas-29cGgrg5.d.ts} +6 -4
  4. package/dist/{auth-BhWqdyb-.d.ts → auth-DtdYjrZ9.d.ts} +51 -21
  5. package/dist/{caller-CXTFswBO.d.ts → caller-BTwGl7vm.d.ts} +296 -33
  6. package/dist/chunk-2FDSKLBO.js +82 -0
  7. package/dist/chunk-2FDSKLBO.js.map +1 -0
  8. package/dist/{chunk-RT6OILOI.js → chunk-4D2DOGAW.js} +2 -14
  9. package/dist/chunk-4D2DOGAW.js.map +1 -0
  10. package/dist/{chunk-OECZPRPW.js → chunk-64I6MYBC.js} +162 -775
  11. package/dist/chunk-64I6MYBC.js.map +1 -0
  12. package/dist/chunk-7473HK24.js +17 -0
  13. package/dist/chunk-7473HK24.js.map +1 -0
  14. package/dist/chunk-7Y5ROPXL.js +33 -0
  15. package/dist/chunk-7Y5ROPXL.js.map +1 -0
  16. package/dist/chunk-A2RBQD66.js +7 -0
  17. package/dist/chunk-A2RBQD66.js.map +1 -0
  18. package/dist/chunk-AK23XSWD.js +16 -0
  19. package/dist/chunk-AK23XSWD.js.map +1 -0
  20. package/dist/chunk-ASAMUGHG.js +651 -0
  21. package/dist/chunk-ASAMUGHG.js.map +1 -0
  22. package/dist/{chunk-B2P7AZ25.js → chunk-CYE4UBOB.js} +6 -4
  23. package/dist/chunk-CYE4UBOB.js.map +1 -0
  24. package/dist/chunk-DKE6QO26.js +136 -0
  25. package/dist/chunk-DKE6QO26.js.map +1 -0
  26. package/dist/chunk-DNP65TD5.js +832 -0
  27. package/dist/chunk-DNP65TD5.js.map +1 -0
  28. package/dist/{chunk-H23KEFBA.js → chunk-DUBU3BB3.js} +30 -1
  29. package/dist/chunk-DUBU3BB3.js.map +1 -0
  30. package/dist/chunk-GYLWEJHW.js +19 -0
  31. package/dist/chunk-GYLWEJHW.js.map +1 -0
  32. package/dist/{chunk-SHEGLZEP.js → chunk-I3JCFS7H.js} +34 -13
  33. package/dist/chunk-I3JCFS7H.js.map +1 -0
  34. package/dist/{chunk-X5CWPX7H.js → chunk-IIQ3WMJK.js} +13 -2
  35. package/dist/chunk-IIQ3WMJK.js.map +1 -0
  36. package/dist/{chunk-RZTP4IEW.js → chunk-JVTONQFI.js} +6339 -4226
  37. package/dist/chunk-JVTONQFI.js.map +1 -0
  38. package/dist/{chunk-QEKVYG5F.js → chunk-KKQANRLG.js} +2 -2
  39. package/dist/chunk-KKQANRLG.js.map +1 -0
  40. package/dist/{chunk-CDKG4TFA.js → chunk-MPQAT55F.js} +38 -205
  41. package/dist/chunk-MPQAT55F.js.map +1 -0
  42. package/dist/chunk-NO4M6PO5.js +101 -0
  43. package/dist/chunk-NO4M6PO5.js.map +1 -0
  44. package/dist/{chunk-TBMKJ26R.js → chunk-NW6U45HX.js} +3 -1
  45. package/dist/chunk-NW6U45HX.js.map +1 -0
  46. package/dist/chunk-NX2V6SIL.js +202 -0
  47. package/dist/chunk-NX2V6SIL.js.map +1 -0
  48. package/dist/{chunk-ZIVPHYRG.js → chunk-NZHA4TZD.js} +72 -175
  49. package/dist/chunk-NZHA4TZD.js.map +1 -0
  50. package/dist/{chunk-2PWMUVC7.js → chunk-OCZJ4EYV.js} +518 -27
  51. package/dist/chunk-OCZJ4EYV.js.map +1 -0
  52. package/dist/{chunk-REX5YPHX.js → chunk-PHCURHC7.js} +76 -21
  53. package/dist/chunk-PHCURHC7.js.map +1 -0
  54. package/dist/{chunk-4YO5H5ON.js → chunk-Q4OGQM5I.js} +4 -10
  55. package/dist/chunk-Q4OGQM5I.js.map +1 -0
  56. package/dist/chunk-QSU47XEX.js +13 -0
  57. package/dist/chunk-QSU47XEX.js.map +1 -0
  58. package/dist/{chunk-DD4SKCNO.js → chunk-RZXJTDH5.js} +13 -2
  59. package/dist/chunk-RZXJTDH5.js.map +1 -0
  60. package/dist/{chunk-Z2LZ7IZ7.js → chunk-SN3OZVEQ.js} +5 -31
  61. package/dist/chunk-SN3OZVEQ.js.map +1 -0
  62. package/dist/{chunk-4DIQHASK.js → chunk-SQ5VUG77.js} +28 -8
  63. package/dist/chunk-SQ5VUG77.js.map +1 -0
  64. package/dist/chunk-STH5DLQP.js +1192 -0
  65. package/dist/chunk-STH5DLQP.js.map +1 -0
  66. package/dist/{chunk-QCHKWIRS.js → chunk-VZFJTKIW.js} +13 -13
  67. package/dist/{chunk-QCHKWIRS.js.map → chunk-VZFJTKIW.js.map} +1 -1
  68. package/dist/{chunk-VSQBHPWH.js → chunk-X4ZU4QH5.js} +54 -22
  69. package/dist/chunk-X4ZU4QH5.js.map +1 -0
  70. package/dist/chunk-XD7SRDUY.js +188 -0
  71. package/dist/chunk-XD7SRDUY.js.map +1 -0
  72. package/dist/chunk-XH2IZVLT.js +62 -0
  73. package/dist/chunk-XH2IZVLT.js.map +1 -0
  74. package/dist/chunk-Z7I6SONO.js +825 -0
  75. package/dist/chunk-Z7I6SONO.js.map +1 -0
  76. package/dist/cli/quickdraw-docs.js +132 -112
  77. package/dist/cli/quickdraw-docs.js.map +1 -1
  78. package/dist/client/core.d.ts +387 -0
  79. package/dist/client/core.js +109 -0
  80. package/dist/client/index.d.ts +52 -203
  81. package/dist/client/index.js +59 -218
  82. package/dist/client/index.js.map +1 -1
  83. package/dist/{clientTypes-Bc-G8n-U.d.ts → clientTypes-CUUNJwWs.d.ts} +3 -3
  84. package/dist/{connection-Cr1BU07W.d.ts → connection-2EeQ_Et0.d.ts} +22 -11
  85. package/dist/{createServer-CP9eDHZZ.d.ts → createServer-Bx5HOcYK.d.ts} +67 -17
  86. package/dist/{createTestApp-UKVGEmOf.d.ts → createTestApp-DAMANT79.d.ts} +46 -13
  87. package/dist/{envelope-Cp3YcVxC.d.ts → envelope-BMBaxppV.d.ts} +216 -10
  88. package/dist/{http-Djj2Inuu.d.ts → http-YzLAar3w.d.ts} +76 -60
  89. package/dist/index.d.ts +8 -7
  90. package/dist/index.js +82 -25
  91. package/dist/index.js.map +1 -1
  92. package/dist/{infer-Teo1xpsd.d.ts → infer-Br62cnyW.d.ts} +82 -11
  93. package/dist/logger-Yz93KzdV.d.ts +30 -0
  94. package/dist/{metrics-BdBu5o8G.d.ts → metrics-CTVpw0gu.d.ts} +2 -2
  95. package/dist/origins-kvpxnVcW.d.ts +8 -0
  96. package/dist/parser.js +2 -1
  97. package/dist/prisma/index.d.ts +14 -7
  98. package/dist/prisma/index.js +279 -70
  99. package/dist/prisma/index.js.map +1 -1
  100. package/dist/{rateLimit-ZMIZJUZF.js → rateLimit-5RKBFLUR.js} +2 -2
  101. package/dist/rateLimit-5RKBFLUR.js.map +1 -0
  102. package/dist/remote-N7OUAGOW.js +15 -0
  103. package/dist/remote-N7OUAGOW.js.map +1 -0
  104. package/dist/server/auth/index.d.ts +82 -33
  105. package/dist/server/auth/index.js +277 -270
  106. package/dist/server/auth/index.js.map +1 -1
  107. package/dist/server/express/index.d.ts +9 -2
  108. package/dist/server/express/index.js +1 -1
  109. package/dist/server/index.d.ts +268 -84
  110. package/dist/server/index.js +664 -418
  111. package/dist/server/index.js.map +1 -1
  112. package/dist/server/mcp/index.d.ts +89 -27
  113. package/dist/server/mcp/index.js +281 -62
  114. package/dist/server/mcp/index.js.map +1 -1
  115. package/dist/server/otel.d.ts +3 -3
  116. package/dist/{sharing-DVa8W-yR.d.ts → sharing-BrsmlDv_.d.ts} +3 -3
  117. package/dist/testing/client.d.ts +14 -14
  118. package/dist/testing/client.js +19 -10
  119. package/dist/testing/client.js.map +1 -1
  120. package/dist/testing/index.d.ts +50 -13
  121. package/dist/testing/index.js +113 -21
  122. package/dist/testing/index.js.map +1 -1
  123. package/dist/testing/mock.d.ts +5 -5
  124. package/dist/testing/mock.js +17 -9
  125. package/dist/tracked-BR5b2O-U.d.ts +43 -0
  126. package/dist/{types-Cu0vmx7c.d.ts → types-CLRCQGnO.d.ts} +4 -3
  127. package/dist/types-tMagHbjy.d.ts +151 -0
  128. package/dist/utils/index.d.ts +16 -4
  129. package/dist/utils/index.js +6 -3
  130. package/package.json +5 -1
  131. package/dist/chunk-2PWMUVC7.js.map +0 -1
  132. package/dist/chunk-4DIQHASK.js.map +0 -1
  133. package/dist/chunk-4YO5H5ON.js.map +0 -1
  134. package/dist/chunk-B2P7AZ25.js.map +0 -1
  135. package/dist/chunk-CDKG4TFA.js.map +0 -1
  136. package/dist/chunk-DD4SKCNO.js.map +0 -1
  137. package/dist/chunk-H23KEFBA.js.map +0 -1
  138. package/dist/chunk-OECZPRPW.js.map +0 -1
  139. package/dist/chunk-QEKVYG5F.js.map +0 -1
  140. package/dist/chunk-REX5YPHX.js.map +0 -1
  141. package/dist/chunk-RT6OILOI.js.map +0 -1
  142. package/dist/chunk-RZTP4IEW.js.map +0 -1
  143. package/dist/chunk-SHEGLZEP.js.map +0 -1
  144. package/dist/chunk-TBMKJ26R.js.map +0 -1
  145. package/dist/chunk-VSQBHPWH.js.map +0 -1
  146. package/dist/chunk-X5CWPX7H.js.map +0 -1
  147. package/dist/chunk-YSWH776Y.js +0 -13
  148. package/dist/chunk-YSWH776Y.js.map +0 -1
  149. package/dist/chunk-Z2LZ7IZ7.js.map +0 -1
  150. package/dist/chunk-ZIVPHYRG.js.map +0 -1
  151. package/dist/logger-BY28acW-.d.ts +0 -16
  152. /package/dist/{rateLimit-ZMIZJUZF.js.map → client/core.js.map} +0 -0
@@ -1,10 +1,10 @@
1
- import { ad as Version, a5 as Revision } from './envelope-Cp3YcVxC.js';
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-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';
1
+ 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, aB as ServerChannelName, ab as ChannelPayloadOf, aa as ChannelName, aC as ServerStreamName, az as StreamName, am as IsScopedStream, ay as StreamItemOf, ae as ContractMap, ap as MethodName, al as InputOf, ar as OutputOf } from './infer-Br62cnyW.js';
2
+ import { P as Principal, Q as QuickdrawTypes, M as MaybePromise, b as PrincipalOf, a as McpContext, T as Transport, C as ContextExtensionOf, c as McpContextOf, D as DbOf } from './types-CLRCQGnO.js';
3
+ import { L as Logger } from './logger-Yz93KzdV.js';
4
+ import { aA as Version, as as Revision } from './envelope-BMBaxppV.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" | "tiered-field-in-output" | "resolver-without-reads";
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" | "untracked-access-model" | "shared-principal" | "channel-without-requires";
8
8
  /** One development warning. */
9
9
  interface DevWarning {
10
10
  readonly kind: DevWarningKind;
@@ -14,8 +14,8 @@ interface DevWarning {
14
14
  /**
15
15
  * What the warning is about within its call, for the once-only rule: the
16
16
  * model of an ambient write, the model, field and operation of a nested
17
- * write, the connection of a repeated call. Empty for the kinds that are
18
- * once per call site.
17
+ * write, the connection of a repeated call, the `ctx` member a shared run
18
+ * read. Empty for the kinds that are once per call site.
19
19
  */
20
20
  readonly subject?: string;
21
21
  /** What happened and what to do instead. */
@@ -95,11 +95,25 @@ interface StorageAdapter {
95
95
  registerInterest(model: string, columns: readonly string[]): void;
96
96
  /** The columns registered for `model`. */
97
97
  interestOf(model: string): readonly string[];
98
+ /**
99
+ * Declares that rows of `model` are the entries of a `via` collection over
100
+ * the junction `link` names: a `delete` or `deleteMany` of `model` reads
101
+ * the junction rows linking the rows it deletes just before, through the
102
+ * open transaction when there is one, and reports them in each write's
103
+ * `links`, so a flush tells only the scopes a deleted entry was linked to
104
+ * (the 5.1 audit's COLL-3). A link committed between that read and the
105
+ * delete is not read (`prisma/links.ts`). Optional: without it a deleted
106
+ * entry left scopes nobody can name, and every subscribed scope of its
107
+ * collection is told it was removed.
108
+ */
109
+ registerLinks?(model: string, link: LinkInterest): void;
110
+ /** The junctions registered for `model` (`registerLinks`). */
111
+ linksOf?(model: string): readonly LinkInterest[];
98
112
  /**
99
113
  * True where the caller runs inside an open transaction of this adapter,
100
- * whose reads see writes that may still roll back. The access cache keeps
101
- * nothing read there; an adapter without this method gets no access cache
102
- * across requests.
114
+ * whose reads see writes that may still roll back. The access cache
115
+ * neither reads nor keeps anything there; an adapter without this method
116
+ * gets no access cache across requests.
103
117
  */
104
118
  inTransaction?(): boolean;
105
119
  /**
@@ -110,6 +124,15 @@ interface StorageAdapter {
110
124
  * cursor shows holding null.
111
125
  */
112
126
  nullable?(model: string, column: string): Promise<boolean>;
127
+ /**
128
+ * Whether the adapter can key `model`'s writes: false for a model without
129
+ * an `id` column (a table keyed by a composite `@@id`), whose writes are
130
+ * never tracked. `createServer` asks it, at start, for every model an
131
+ * access policy, a membership table or a `via` junction depends on, and
132
+ * names each it cannot track (the `untracked-access-model` warning). An
133
+ * adapter without it is not asked.
134
+ */
135
+ trackable?(model: string): Promise<boolean>;
113
136
  /** The units of work that record this adapter's writes; the dispatcher uses them. */
114
137
  readonly unitOfWork: UnitOfWorkFactory;
115
138
  }
@@ -119,6 +142,17 @@ interface StorageAdapter {
119
142
  * `trackPrisma` still carries it.
120
143
  */
121
144
  declare function storageOf(db: unknown): StorageAdapter | undefined;
145
+ /**
146
+ * A junction whose rows link an entry model's rows to the scopes of a `via`
147
+ * collection (`registerLinks`): its model, as the client names it, the
148
+ * column holding the entry's id, and the columns a delete reads with it (the
149
+ * scope column, and the role columns of a members table's rule).
150
+ */
151
+ interface LinkInterest {
152
+ readonly model: string;
153
+ readonly entry: string;
154
+ readonly columns: readonly string[];
155
+ }
122
156
 
123
157
  /** A principal's level on one row: an access level, or `null` for none. */
124
158
  type RowLevel = AccessLevel | null;
@@ -301,7 +335,22 @@ type CollectionScope = {
301
335
  readonly scope: string;
302
336
  /** A junction write sends its entry again to every scope that still holds it (`via`'s `refreshEntry`). */
303
337
  readonly refreshEntry: boolean;
338
+ /**
339
+ * The junction is the item service's `members` table: only links whose
340
+ * role gives at least the collection's `access` count, by one rule per
341
+ * role column the policy's `members` reads of the table use (a link
342
+ * any rule counts counts), and a write of a role column can move the
343
+ * link in or out.
344
+ */
345
+ readonly levels?: readonly LinkLevel[];
304
346
  };
347
+ /** One role rule of a `via` scope over the item service's `members` table. */
348
+ interface LinkLevel {
349
+ /** The junction's role column. */
350
+ readonly column: string;
351
+ /** The stored roles that give at least the collection's `access`; none: no link counts by this column. */
352
+ readonly roles: readonly string[];
353
+ }
305
354
  /** One collection of a service: its contract declaration and the service's option, checked. */
306
355
  interface ServiceCollection {
307
356
  readonly name: string;
@@ -319,6 +368,14 @@ interface ServiceCollection {
319
368
  readonly access: AccessLevel;
320
369
  /** The contract of the rows scope values are ids of; `undefined` for a `"self"` scope. */
321
370
  readonly anchor: AnyContract | undefined;
371
+ /**
372
+ * The service-wide grant on the collection's service every subscriber needs
373
+ * besides the anchor or self check (`requires: { service: L }`);
374
+ * `undefined` for none.
375
+ */
376
+ readonly requires: {
377
+ readonly service: AccessLevel;
378
+ } | undefined;
322
379
  /** More changed rows than this for one scope in one flush go out as one `reset`. */
323
380
  readonly bulkThreshold: number;
324
381
  /**
@@ -389,6 +446,14 @@ interface ServiceMethod {
389
446
  * caller is left to the form.
390
447
  */
391
448
  readonly kinds: readonly string[] | undefined;
449
+ /**
450
+ * The method's requirement on the caller's claims (RFC 0003 section 4.1,
451
+ * `access/claims.ts`), checked right after `kinds`, outside the access
452
+ * engine: `UNAUTHENTICATED` without a principal, `FORBIDDEN` unless it
453
+ * returns `true`. It adds to the access form, and no grant passes it, a
454
+ * service-wide `Admin` grant included. `undefined` when not declared.
455
+ */
456
+ readonly claims: ClaimsCheck | undefined;
392
457
  readonly handler: AnyHandler;
393
458
  readonly share: ShareMode | undefined;
394
459
  /** How long a shared result is reused after its run, in milliseconds. */
@@ -466,6 +531,8 @@ interface Service<T extends QuickdrawTypes = QuickdrawTypes, C extends AnyContra
466
531
  * rooms. `undefined` when the service declares none.
467
532
  */
468
533
  readonly onRoomLeave: RoomLeaveHandler | undefined;
534
+ /** The service's own `onHostLeave`: `createServer` runs it, beside the server's own, when a room's host leaves. */
535
+ readonly onHostLeave?: HostLeaveHandler | undefined;
469
536
  /** Type-only: the app types the service was defined with. Never set. */
470
537
  readonly "~types"?: T;
471
538
  }
@@ -524,6 +591,11 @@ interface ScopeAccess<Input = never> {
524
591
  readonly service?: never;
525
592
  readonly kind?: never;
526
593
  }
594
+ /**
595
+ * A method's `claims`, as the pipeline calls it (`claims.ts`): `true` when
596
+ * the caller's claims allow the call with `input`.
597
+ */
598
+ type ClaimsCheck = (claims: Principal["claims"], input: unknown) => boolean;
527
599
  /**
528
600
  * `custom(fn)`: the call passes when `fn(ctx, input)` resolves `true`. The
529
601
  * caller must be authenticated before `fn` runs, so `ctx.principal` is never
@@ -598,11 +670,27 @@ interface RowAccess {
598
670
  allows(form: EntryAccess | ScopeAccess, request: AccessRequest): MaybePromise<boolean>;
599
671
  }
600
672
 
673
+ /**
674
+ * A row whose access may have changed: who may now see more or less of it.
675
+ * `id` is the row (the anchor subscriptions record); without it, any row of
676
+ * the service. `userId` is the user whose level may have changed; without it,
677
+ * anyone's.
678
+ */
679
+ interface AccessChange {
680
+ /** The service name. */
681
+ readonly service: string;
682
+ readonly id?: string;
683
+ readonly userId?: string;
684
+ }
685
+ /** Receives access changes; a promise it returns is awaited before the flush moves on. */
686
+ type AccessChangeListener = (change: AccessChange) => void | PromiseLike<void>;
687
+
601
688
  /**
602
689
  * Who is online and who is in a room (RFC 0003 section 12.5): `ctx.presence`,
603
690
  * `server.presence`, `qd.presence`. Every answer is a promise: on a server
604
691
  * with the in-memory adapter it comes from this process's sockets; behind a
605
- * cluster adapter (Redis) it asks every node (`io.in(room).fetchSockets()`).
692
+ * cluster adapter (Redis) it asks the other nodes too, which answer with
693
+ * user ids (or whether they have a socket of the user) alone.
606
694
  */
607
695
  interface Presence {
608
696
  /** True while the user has a socket connected (on any node). */
@@ -614,9 +702,14 @@ interface Presence {
614
702
  * the 100,000 users seen last).
615
703
  */
616
704
  lastSeen(userId: string): Promise<number | null>;
617
- /** How many users have a socket in `room`. Anonymous sockets do not count. */
705
+ /**
706
+ * How many users have a socket in app room `room` that `ctx.rooms.join`
707
+ * put there (a raw `socket.join` does not count on this process).
708
+ * Anonymous sockets do not count. A framework room (`qd:`, `user:`) or a
709
+ * name with the shape of a socket id is `VALIDATION`.
710
+ */
618
711
  count(room: string): Promise<number>;
619
- /** The ids of the users with a socket in `room`, each once. */
712
+ /** The ids of the users `count` counts in `room`, each once. */
620
713
  users(room: string): Promise<string[]>;
621
714
  }
622
715
  /** Whose sockets `rooms.leave(room, target)` takes out of a room: every socket of one user. */
@@ -639,8 +732,9 @@ interface ServerRooms {
639
732
  /**
640
733
  * Sends the contract's event to every socket in `room` as `qd:event`, on
641
734
  * every node. The payload is checked against the event's schema first: a
642
- * payload that fails it throws `INTERNAL` and nothing is sent. Without a
643
- * server it does nothing more.
735
+ * payload that fails it throws `INTERNAL` and nothing is sent. A room of
736
+ * the framework's (`qd:`, `user:`) throws `VALIDATION`: send to a user's
737
+ * sockets with `emitToUser`. Without a server it does nothing more.
644
738
  */
645
739
  emit<C extends AnyContract, E extends EventName<C>>(room: string, contract: C, event: E, payload: EventPayloadOf<C, E>): void;
646
740
  /** `emit` to every socket of one user (their `user:{userId}` room), on every node. */
@@ -670,6 +764,19 @@ interface ServerRooms {
670
764
  * it is 0.
671
765
  */
672
766
  size(room: string): number;
767
+ /**
768
+ * The host of app room `room` (host mode, RFC 0003 section 12.5): the id
769
+ * of the socket that serves the room's host-served channels and streams,
770
+ * or `null` when it has none. This node's record, which every node of a
771
+ * cluster keeps. Room names are checked as `join` checks them.
772
+ */
773
+ hostOf(room: string): string | null;
774
+ /**
775
+ * Leaves app room `room` without a host, on every node: its sockets hear
776
+ * `qd:host [room, null]`. Nothing happens for a room without one, and
777
+ * `onHostLeave` does not run (the host did not leave).
778
+ */
779
+ clearHost(room: string): void;
673
780
  }
674
781
  /**
675
782
  * `ctx.rooms` (RFC 0003 sections 3, 12.5 and 15): app-defined rooms the
@@ -685,12 +792,28 @@ interface ContextRooms extends ServerRooms {
685
792
  * nothing, for a call that did not arrive over a socket (HTTP, MCP, in
686
793
  * process). Room names starting with `qd:` or `user:` are refused with
687
794
  * `VALIDATION` (those rooms are joined only through their authorized
688
- * paths), as are empty names and names over 256 characters; a socket in
689
- * 100 app rooms is refused another with `CONFLICT`.
795
+ * paths), as are names with the shape of a Socket.IO socket id (20
796
+ * letters, digits, `_` or `-`: every socket is alone in the room of its
797
+ * id), empty names and names over 256 characters; a socket in 100 app
798
+ * rooms is refused another with `CONFLICT`.
690
799
  */
691
800
  join(room: string): boolean;
692
801
  /** Takes the calling socket out of an app room; `false` when it was not in it, or the call has no socket. */
693
802
  leave(room: string): boolean;
803
+ /**
804
+ * Makes the calling socket the host of app room `room` (host mode, RFC
805
+ * 0003 section 12.5): the room's sockets hear `qd:host [room, socketId]`
806
+ * on every node, and so does every socket that joins it later, until the
807
+ * host leaves the room (`onHostLeave` runs), another socket is made its
808
+ * host, or `clearHost` runs. Its peers then send the room's host-served
809
+ * channels to it and subscribe to its host-served streams over peer links.
810
+ * Returns `false`, doing nothing, for a call that did not arrive over a
811
+ * socket. The socket must be in the room (`FORBIDDEN`) and signed in
812
+ * (`UNAUTHENTICATED`), and the server must serve peer links
813
+ * (`createServer({ peers: true })`, else `INTERNAL`). Room names are
814
+ * checked as `join` checks them.
815
+ */
816
+ setHost(room: string): boolean;
694
817
  /** See {@link ServerRooms.leave}: every socket of the user, on every node, from any call. */
695
818
  leave(room: string, target: RoomTarget): Promise<void>;
696
819
  }
@@ -734,10 +857,31 @@ interface RoomLeave<P = Principal> {
734
857
  * error it throws is logged; a server's `close()` waits for it.
735
858
  */
736
859
  type RoomLeaveHandler<P = Principal> = (leave: RoomLeave<P>, ctx: RunContext) => void | PromiseLike<void>;
860
+ /** The host of an app room left it (host mode): what `onHostLeave` hears. */
861
+ interface HostLeave<P = Principal> {
862
+ readonly room: string;
863
+ /** The host socket that left. */
864
+ readonly socketId: string;
865
+ /** Its principal. */
866
+ readonly principal: P | null;
867
+ /** Its own leave, a removal, or a disconnect. */
868
+ readonly reason: RoomLeaveReason;
869
+ }
870
+ /**
871
+ * `onHostLeave` (a service's, beside `onRoomLeave`, and `createServer`'s):
872
+ * called once when the host of an app room leaves it, is taken out of it or
873
+ * disconnects, on the node that held it, after the room heard
874
+ * `qd:host [room, null]`, in a unit of work of its own. The room has no
875
+ * host until a method sets one again: the app decides what follows (another
876
+ * host, the end of the game).
877
+ */
878
+ type HostLeaveHandler<P = Principal> = (leave: HostLeave<P>, ctx: RunContext) => void | PromiseLike<void>;
737
879
  /** The arguments of a stream's `push`: `(scope, item)` for a scoped stream, `(item)` for a global one. */
738
880
  type StreamPushArgs<C extends AnyContract, K extends StreamName<C>> = IsScopedStream<C, K> extends true ? [scope: string, item: StreamItemOf<C, K>] : [item: StreamItemOf<C, K>];
739
881
  /** The arguments of a stream's `pushMany`: `(scope, items)` for a scoped stream, `(items)` for a global one. */
740
882
  type StreamPushManyArgs<C extends AnyContract, K extends StreamName<C>> = IsScopedStream<C, K> extends true ? [scope: string, items: readonly StreamItemOf<C, K>[]] : [items: readonly StreamItemOf<C, K>[]];
883
+ /** The arguments of a stream's `subscribers`: `(scope)` for a scoped stream, `()` for a global one. */
884
+ type StreamSubscribersArgs<C extends AnyContract, K extends StreamName<C>> = IsScopedStream<C, K> extends true ? [scope: string] : [];
741
885
  /** What `qd.stream(contract, name)` returns. */
742
886
  interface StreamHandle<C extends AnyContract, K extends StreamName<C>> {
743
887
  /**
@@ -754,6 +898,19 @@ interface StreamHandle<C extends AnyContract, K extends StreamName<C>> {
754
898
  * with nothing kept or sent. The batch form of a `push` in a loop.
755
899
  */
756
900
  pushMany(...args: StreamPushManyArgs<C, K>): void;
901
+ /**
902
+ * How many sockets subscribe to the feed (`subscribers(scope)`, or
903
+ * `subscribers()` for a global stream), on every node, anonymous ones
904
+ * included: a job skips the work nobody watches. These are the sockets in
905
+ * the feed's room, the ones a push reaches: a subscribe counts from when it
906
+ * joins the feed, once it is authorized. Behind a cluster adapter the
907
+ * other nodes answer one request with a count each: an answer that does
908
+ * not arrive within `cluster.timeoutMs` counts as 0, and so does a node
909
+ * of an earlier version, which does not know the request. A host-served stream (`serve: "host"`) has no feed on
910
+ * the server, so, as for `push`, its handle refuses this with a
911
+ * `TypeError`: only the host's client knows who subscribes.
912
+ */
913
+ subscribers(...args: StreamSubscribersArgs<C, K>): Promise<number>;
757
914
  }
758
915
  /**
759
916
  * What a stream's `seed` function receives besides the scope: who is
@@ -815,7 +972,7 @@ type StreamValidation = "always" | "development";
815
972
  * subset): a seed computed at subscribe time, and when items are checked.
816
973
  */
817
974
  type StreamOptions<T extends QuickdrawTypes, C extends AnyContract> = {
818
- readonly [K in StreamName<C>]?: StreamImplementation<T, C, K>;
975
+ readonly [K in ServerStreamName<C>]?: StreamImplementation<T, C, K>;
819
976
  };
820
977
  /** A stream's `seed` function as the subscribe path calls it, whatever its declared types. */
821
978
  type AnyStreamSeed = (scope: string | undefined, ctx: StreamSeedContext) => unknown;
@@ -850,7 +1007,12 @@ interface ChannelContext<P = Principal, Room extends string | undefined = string
850
1007
  type ChannelHandler<T extends QuickdrawTypes, Payload, Room extends string | undefined = string | undefined> = (payload: Payload, ctx: ChannelContext<PrincipalOf<T>, Room>) => void | PromiseLike<void>;
851
1008
  /** One channel's implementation in `defineService`: its handler, or `{ access, handler }`. */
852
1009
  type ChannelImplementation<T extends QuickdrawTypes, Payload, Room extends string | undefined = string | undefined> = ChannelHandler<T, Payload, Room> | {
853
- /** Default `"authenticated"`. */
1010
+ /**
1011
+ * Default `"authenticated"`. A channel whose contract declares no
1012
+ * `requires` takes a message from every signed-in socket: declaring
1013
+ * `access` (even `"authenticated"`) says so on purpose, and silences
1014
+ * the `channel-without-requires` warning the server raises otherwise.
1015
+ */
854
1016
  readonly access?: ChannelAccess;
855
1017
  readonly handler: ChannelHandler<T, Payload, Room>;
856
1018
  };
@@ -860,12 +1022,15 @@ type ChannelRoomOf<C extends AnyContract, K extends ChannelName<C>> = C["channel
860
1022
  readonly room: string | object;
861
1023
  };
862
1024
  } ? string : undefined;
863
- /** `defineService`'s `channels`: one implementation per channel of the contract. */
1025
+ /**
1026
+ * `defineService`'s `channels`: one implementation per channel of the
1027
+ * contract the server handles (a host-served one takes none).
1028
+ */
864
1029
  type ChannelOptions<T extends QuickdrawTypes, C extends AnyContract> = {
865
- readonly [K in ChannelName<C>]: ChannelImplementation<T, ChannelPayloadOf<C, K>, ChannelRoomOf<C, K>>;
1030
+ readonly [K in ServerChannelName<C>]: ChannelImplementation<T, ChannelPayloadOf<C, K>, ChannelRoomOf<C, K>>;
866
1031
  };
867
1032
  /** `{ channels }` is required when the contract declares channels: every message needs a handler. */
868
- type ChannelsRequired<C extends AnyContract> = [ChannelName<C>] extends [never] ? unknown : {
1033
+ type ChannelsRequired<C extends AnyContract> = [ServerChannelName<C>] extends [never] ? unknown : {
869
1034
  readonly channels: unknown;
870
1035
  };
871
1036
  /**
@@ -920,6 +1085,12 @@ interface ServiceChannel {
920
1085
  readonly prefix: string;
921
1086
  } | undefined;
922
1087
  readonly access: ChannelAccess;
1088
+ /**
1089
+ * True when the service named `access` for the channel (`{ access,
1090
+ * handler }`), not a bare handler or `{ handler }`: a channel without
1091
+ * `requires` warns `channel-without-requires` unless it did.
1092
+ */
1093
+ readonly accessDeclared: boolean;
923
1094
  /**
924
1095
  * The kinds of principal the channel's service admits, as a set for the
925
1096
  * per-message check: a message from a socket of another kind, or without
@@ -950,6 +1121,8 @@ interface ServiceStream {
950
1121
  readonly access: AccessForm | undefined;
951
1122
  /** The contract's `access: { room }`: the app room a subscriber's socket must be in. */
952
1123
  readonly room: StreamRoomAccess | undefined;
1124
+ /** True for a stream the room's host pushes (`serve: "host"`): the server neither pushes nor subscribes it. */
1125
+ readonly host: boolean;
953
1126
  }
954
1127
  /** A stream's `access: { room }`, compiled: the app room a subscriber's socket must be in. */
955
1128
  type StreamRoomAccess = {
@@ -972,10 +1145,10 @@ interface TouchOptions {
972
1145
  * `ctx.services`: in-process callers for the app's services (RFC 0003
973
1146
  * sections 3 and 10), by service name: `ctx.services.projectService.get(input)`.
974
1147
  * A call runs the whole pipeline (input check, access, handler, output
975
- * check) as the calling principal with transport `"internal"`, its writes
976
- * join the calling method's unit of work (one flush for both), and it is
977
- * cancelled with `ctx.signal`. Typed by the `contracts` of the app's types,
978
- * like `qd.caller`; untyped without them.
1148
+ * check) as the calling principal with transport `"internal"` and the
1149
+ * calling method's `ctx.mcp`, its writes join the calling method's unit of
1150
+ * work (one flush for both), and it is cancelled with `ctx.signal`. Typed by
1151
+ * the `contracts` of the app's types, like `qd.caller`; untyped without them.
979
1152
  */
980
1153
  type ContextServices<T extends QuickdrawTypes = QuickdrawTypes> = CallerFor<T>;
981
1154
 
@@ -1012,7 +1185,10 @@ interface BaseContext<P = Principal, M = McpContext> {
1012
1185
  /**
1013
1186
  * The fields the MCP bridge's `context` option produced for this call, such
1014
1187
  * as the scopes of the agent's token; typed by `QuickdrawTypes["mcp"]`.
1015
- * `undefined` unless the call arrived over MCP.
1188
+ * `undefined` unless the call arrived over MCP or was made through the
1189
+ * `ctx.services` of a call that has it (nested calls too), which passes
1190
+ * the same object on. Such a call's `transport` is `"internal"`: to tell a
1191
+ * token's call apart, read `ctx.mcp`, not `ctx.transport`.
1016
1192
  */
1017
1193
  readonly mcp?: M;
1018
1194
  /**
@@ -1024,10 +1200,20 @@ interface BaseContext<P = Principal, M = McpContext> {
1024
1200
  */
1025
1201
  touch(target: string | AnyContract, ids: string | readonly string[], options?: TouchOptions): void;
1026
1202
  /**
1027
- * In-process callers for the app's services, as this call's principal:
1028
- * `await ctx.services.projectService.get({ id })`. See {@link ContextServices}.
1203
+ * In-process callers for the app's services, as this call's principal and
1204
+ * with its `ctx.mcp`: `await ctx.services.projectService.get({ id })`. See
1205
+ * {@link ContextServices}.
1029
1206
  */
1030
1207
  readonly services: ContextServices;
1208
+ /**
1209
+ * The caller's levels on rows of the app's services, as the access forms
1210
+ * count them (RFC 0003 section 4.3): `dispatcher.access` bound to
1211
+ * `ctx.principal`, with the grants this call runs with. For a `custom`
1212
+ * form that checks a row its key names only sometimes, or a parent its
1213
+ * input names: `await ctx.access.can(project, input.projectId, "Moderate")`.
1214
+ * See {@link ContextAccess}.
1215
+ */
1216
+ readonly access: ContextAccess;
1031
1217
  /**
1032
1218
  * App-defined rooms the calling socket joins and leaves (`join` answers
1033
1219
  * `false` for a call without a socket), and typed room events: `emit` and
@@ -1112,19 +1298,32 @@ interface WriteRecord {
1112
1298
  /** The written row's id. */
1113
1299
  readonly id: string;
1114
1300
  readonly op: "create" | "update" | "delete";
1115
- /** The fields the write set; `[ANY_FIELD]` when they are unknown. */
1301
+ /**
1302
+ * The fields the write set; `[ANY_FIELD]` when they are unknown. An update
1303
+ * whose data held a nested write, which may set a foreign key through a
1304
+ * relation, lists every interested column of the model besides its keys.
1305
+ */
1116
1306
  readonly fields: readonly string[];
1117
1307
  /**
1118
1308
  * The row before the unit of work wrote it, for its interested columns: an
1119
1309
  * update's old values of the interested columns it changed (read first,
1120
- * narrowly), or a delete's values. A row the unit created has none.
1310
+ * narrowly), or of every interested column when its data held a nested
1311
+ * write, or a delete's values. A row the unit created has none.
1121
1312
  */
1122
1313
  readonly before?: Readonly<Record<string, unknown>>;
1123
1314
  /** The interested columns' values after the write, when the write returned them. */
1124
1315
  readonly after?: Readonly<Record<string, unknown>>;
1316
+ /**
1317
+ * On a delete of a model whose junction links are registered
1318
+ * (`StorageAdapter.registerLinks`): per junction model, the junction rows
1319
+ * that linked the row just before the delete (possibly none), with the
1320
+ * registered columns. Absent when the links were not read.
1321
+ */
1322
+ readonly links?: Readonly<Record<string, readonly Readonly<Record<string, unknown>>[]>>;
1125
1323
  /**
1126
1324
  * Set on a `create` an `upsert` recorded without reading whether its row
1127
- * existed (its `update` set no interested column): the row may have been
1325
+ * existed (its `update` set no interested column and held no nested
1326
+ * write, or the model has no interested columns): the row may have been
1128
1327
  * updated rather than created. Only merging reads it: a row created and
1129
1328
  * deleted in one unit is dropped from the flush only when its create is
1130
1329
  * certain.
@@ -1222,6 +1421,70 @@ interface FlushSink {
1222
1421
  onFlushError?(writes: readonly WriteRecord[], info: FlushInfo, error: unknown): void | Promise<void>;
1223
1422
  }
1224
1423
 
1424
+ /** The dispatcher's view of its services' access policies: `dispatcher.access`. */
1425
+ interface DispatcherAccess {
1426
+ /**
1427
+ * The principal's level on each of `ids` of `service`, as an `{ entry }`
1428
+ * check counts it: the service's policy, or `Admin` on every row when the
1429
+ * principal holds a service-wide `Admin` grant and the service keeps
1430
+ * `adminBypass` on. A grant below `Admin` does not count. A row that is
1431
+ * not found has no level. One batched lookup per policy.
1432
+ */
1433
+ levelsFor(service: AnyContract | string, principal: Principal, ids: readonly string[]): Promise<RowLevels>;
1434
+ /**
1435
+ * A filter for `db.<model>.findMany({ where })` matching exactly the rows of
1436
+ * `service` on which `levelsFor` gives the principal at least `level`:
1437
+ * `{}` (every row) for a service-wide `Admin` with `adminBypass`, or
1438
+ * `"none"` when no row matches.
1439
+ */
1440
+ accessWhere(service: AnyContract | string, principal: Principal, level: AccessLevel): Promise<AccessFilter>;
1441
+ /**
1442
+ * Calls `listener` after each flush whose tracked writes may have changed
1443
+ * someone's access to a row (`AccessChange`), once the cache no longer
1444
+ * holds what they changed. A promise it returns is awaited before the
1445
+ * dispatcher's other flush sinks run. Returns a function that removes it.
1446
+ */
1447
+ onAccessChanged(listener: AccessChangeListener): () => void;
1448
+ }
1449
+ /**
1450
+ * `ctx.access` (RFC 0003 section 4.3): `dispatcher.access` bound to the
1451
+ * call's caller, `ctx.principal`, with the grants that call runs with (its
1452
+ * socket's, or those `qd.caller` loaded). For a `custom(...)` form or a
1453
+ * handler that asks about a row its method's own form does not name: a
1454
+ * payment's loan, a parent named by an optional key. `service` is a
1455
+ * contract or a service name with an access policy on the dispatcher;
1456
+ * another is a `TypeError`. An anonymous caller (a `"public"` method called
1457
+ * without credentials) has no level on any row.
1458
+ *
1459
+ * @example
1460
+ * access: custom(async (ctx, input) => await ctx.access.can(loan, input.loanId, "Moderate")),
1461
+ */
1462
+ interface ContextAccess {
1463
+ /**
1464
+ * The caller's level on each of `ids` of `service`, as an `{ entry }`
1465
+ * check counts it (`dispatcher.access.levelsFor` with `ctx.principal`): a
1466
+ * service-wide `Admin` grant gives `Admin` on every id while the service
1467
+ * keeps `adminBypass` on, and a row that is not found has no level. One
1468
+ * batched lookup per policy. An empty map for an anonymous caller.
1469
+ */
1470
+ levelsFor(service: AnyContract | string, ids: readonly string[]): Promise<RowLevels>;
1471
+ /**
1472
+ * Whether the caller's level on row `id` of `service` is at least `level`,
1473
+ * as `{ entry: level }` decides it on that row: `false` for a row that is
1474
+ * not found (but for a service-wide `Admin` grant, which reaches every id
1475
+ * without reading it, as for `{ entry }`) and for an anonymous caller.
1476
+ */
1477
+ can(service: AnyContract | string, id: string, level: AccessLevel): Promise<boolean>;
1478
+ /**
1479
+ * A filter for `db.<model>.findMany({ where })` matching exactly the rows
1480
+ * of `service` on which `can` gives the caller `level`, for a hand-written
1481
+ * list (`dispatcher.access.accessWhere` with `ctx.principal`): `{}` for a
1482
+ * service-wide `Admin` with `adminBypass`, `"none"` when no row matches
1483
+ * and for an anonymous caller.
1484
+ */
1485
+ where(service: AnyContract | string, level: AccessLevel): Promise<AccessFilter>;
1486
+ }
1487
+
1225
1488
  /** Options of one in-process call. */
1226
1489
  interface CallOptions {
1227
1490
  /** Cancels the call (queries only), like a client's `qd:cancel`. */
@@ -1248,4 +1511,4 @@ type CallerFor<T extends QuickdrawTypes> = T extends {
1248
1511
  readonly contracts: infer Contracts extends ContractMap;
1249
1512
  } ? Caller<Contracts[keyof Contracts]> : Caller<AnyContract>;
1250
1513
 
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 };
1514
+ export { type AffectsLink 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 HostLeaveHandler 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 RunContext 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 AccessFilter as Z, type RowAccess as _, type StorageRow as a, type ProjectedOutput as a0, type Projection as a1, type TierGroup as a2, type Tiers as a3, DEFAULT_BULK_THRESHOLD as a4, type CollectionScope as a5, type ServiceCollection as a6, type ContextAccess as a7, type ContextRooms as a8, type ContextServices as a9, type ParentLink as aA, type PolicyKind as aB, type PolicyReads as aC, type RowLevel as aD, type RowLevels as aE, type AccessChange as aF, type AccessChangeListener as aG, type AccessForm as aH, type AccessRequest as aI, type AuthenticatedAccess as aJ, type EntryAccess as aK, type IdKeyOf as aL, type IdSelector as aM, type ScopeAccess as aN, type ServiceAccess as aO, ANY_FIELD as aP, type UnitOfWork as aQ, type UnitOfWorkScope as aR, type LinkInterest as aS, formatDevWarning as aT, N_PLUS_ONE_STATEMENTS as aU, type TouchOptions as aa, type ChannelAccess as ab, type ChannelContext as ac, type ChannelHandler as ad, type ChannelImplementation as ae, type ChannelRoomOf as af, type HostLeave as ag, type RoomLeave as ah, type RoomLeaveReason as ai, type RoomLeft as aj, type RoomTarget as ak, type ServiceChannel as al, type ServiceStream as am, type StreamImplementation as an, type StreamPushArgs as ao, type StreamPushManyArgs as ap, type StreamSeed as aq, type StreamSeedContext as ar, type StreamSubscribersArgs as as, type StreamValidation as at, type CallOptions as au, type MethodCaller as av, type ServiceCaller as aw, type ForeignColumns as ax, type ForeignColumnsOf as ay, type MembershipRead 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 RunOptions as k, type DispatcherAccess as l, type ServerRooms as m, type StreamHandle as n, type AnyContext as o, type AccessEngine as p, type AppRooms as q, type StreamSubscriptions as r, storageOf as s, type RoomLeaveHandler as t, type PolicyFor as u, type RowForms as v, type AccessFor as w, type HandlerContext as x, type ModelColumn as y, type WatchAccess as z };
@@ -0,0 +1,82 @@
1
+ import {
2
+ MAX_PEER_CANDIDATE_LENGTH,
3
+ MAX_PEER_SIGNAL_LENGTH,
4
+ MAX_SCOPE_LENGTH
5
+ } from "./chunk-X4ZU4QH5.js";
6
+ import {
7
+ isRecord
8
+ } from "./chunk-QSU47XEX.js";
9
+
10
+ // src/protocol/peer.ts
11
+ var MAX_SOCKET_ID_LENGTH = 64;
12
+ function isText(value) {
13
+ return typeof value === "string" && value.length <= MAX_PEER_SIGNAL_LENGTH;
14
+ }
15
+ function isCandidateText(value) {
16
+ return typeof value === "string" && value.length <= MAX_PEER_CANDIDATE_LENGTH;
17
+ }
18
+ function isRoomName(value) {
19
+ return typeof value === "string" && value !== "" && value.length <= MAX_SCOPE_LENGTH;
20
+ }
21
+ function isSocketId(value) {
22
+ return typeof value === "string" && value !== "" && value.length <= MAX_SOCKET_ID_LENGTH;
23
+ }
24
+ function isPeerLabel(value) {
25
+ return value === "rel" || value === "fast";
26
+ }
27
+ function nullable(value, check) {
28
+ return value === void 0 || value === null || check(value);
29
+ }
30
+ function isLineIndex(value) {
31
+ return typeof value === "number" && Number.isSafeInteger(value) && value >= 0;
32
+ }
33
+ function readDescription(value) {
34
+ if (!isRecord(value) || value.type !== "offer" && value.type !== "answer") {
35
+ return void 0;
36
+ }
37
+ return isText(value.sdp) ? { type: value.type, sdp: value.sdp } : void 0;
38
+ }
39
+ function readCandidate(value) {
40
+ if (!isRecord(value) || !isCandidateText(value.candidate)) {
41
+ return void 0;
42
+ }
43
+ const { sdpMid, sdpMLineIndex, usernameFragment } = value;
44
+ if (!nullable(sdpMid, isCandidateText) || !nullable(sdpMLineIndex, isLineIndex) || !nullable(usernameFragment, isCandidateText)) {
45
+ return void 0;
46
+ }
47
+ return {
48
+ candidate: value.candidate,
49
+ ...sdpMid === void 0 ? {} : { sdpMid },
50
+ ...sdpMLineIndex === void 0 ? {} : { sdpMLineIndex },
51
+ ...usernameFragment === void 0 ? {} : { usernameFragment }
52
+ };
53
+ }
54
+ function readSignal(value) {
55
+ if (!isRecord(value)) {
56
+ return void 0;
57
+ }
58
+ const { sdp, ice, relay, bye } = value;
59
+ if ([sdp, ice, relay, bye].filter((member) => member !== void 0).length !== 1) {
60
+ return void 0;
61
+ }
62
+ if (sdp !== void 0) {
63
+ const description = readDescription(sdp);
64
+ return description === void 0 ? void 0 : { sdp: description };
65
+ }
66
+ if (ice !== void 0) {
67
+ const candidate = readCandidate(ice);
68
+ return candidate === void 0 ? void 0 : { ice: candidate };
69
+ }
70
+ if (relay === true) {
71
+ return { relay: true };
72
+ }
73
+ return bye === true ? { bye: true } : void 0;
74
+ }
75
+
76
+ export {
77
+ isRoomName,
78
+ isSocketId,
79
+ isPeerLabel,
80
+ readSignal
81
+ };
82
+ //# sourceMappingURL=chunk-2FDSKLBO.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/protocol/peer.ts"],"sourcesContent":["// Reading peer link signals (RFC 0003 section 12.5), for the server that\n// forwards them and the client that applies them. A signal is copied member\n// by member, so what goes on is only what was read: a session description\n// of at most `MAX_PEER_SIGNAL_LENGTH` characters, an ICE candidate whose\n// strings hold at most `MAX_PEER_CANDIDATE_LENGTH`, `relay` or `bye`.\n// Internal: neither the package root nor `./client` exports these.\n\nimport type { PeerCandidate, PeerDescription, PeerLabel, PeerSignal } from \"./envelope\";\nimport { isRecord } from \"./guards\";\nimport { MAX_PEER_CANDIDATE_LENGTH, MAX_PEER_SIGNAL_LENGTH, MAX_SCOPE_LENGTH } from \"./version\";\n\n/** The longest socket id a peer frame may name. Socket.IO's are 20 characters. */\nexport const MAX_SOCKET_ID_LENGTH = 64;\n\n/** True for a string of at most `MAX_PEER_SIGNAL_LENGTH` characters, empty included. */\nfunction isText(value: unknown): value is string {\n return typeof value === \"string\" && value.length <= MAX_PEER_SIGNAL_LENGTH;\n}\n\n/** True for a string of at most `MAX_PEER_CANDIDATE_LENGTH` characters, empty included. */\nfunction isCandidateText(value: unknown): value is string {\n return typeof value === \"string\" && value.length <= MAX_PEER_CANDIDATE_LENGTH;\n}\n\n/** True for what may name an app room: 1 to `MAX_SCOPE_LENGTH` characters. */\nexport function isRoomName(value: unknown): value is string {\n return typeof value === \"string\" && value !== \"\" && value.length <= MAX_SCOPE_LENGTH;\n}\n\n/** True for what may name a socket: 1 to `MAX_SOCKET_ID_LENGTH` characters. */\nexport function isSocketId(value: unknown): value is string {\n return typeof value === \"string\" && value !== \"\" && value.length <= MAX_SOCKET_ID_LENGTH;\n}\n\n/** True for `rel` and `fast`. */\nexport function isPeerLabel(value: unknown): value is PeerLabel {\n return value === \"rel\" || value === \"fast\";\n}\n\n/** True when `value` is absent, `null`, or passes `check`. */\nfunction nullable(value: unknown, check: (inner: unknown) => boolean): boolean {\n return value === undefined || value === null || check(value);\n}\n\nfunction isLineIndex(value: unknown): boolean {\n return typeof value === \"number\" && Number.isSafeInteger(value) && value >= 0;\n}\n\nfunction readDescription(value: unknown): PeerDescription | undefined {\n if (!isRecord(value) || (value.type !== \"offer\" && value.type !== \"answer\")) {\n return undefined;\n }\n return isText(value.sdp) ? { type: value.type, sdp: value.sdp } : undefined;\n}\n\nfunction readCandidate(value: unknown): PeerCandidate | undefined {\n if (!isRecord(value) || !isCandidateText(value.candidate)) {\n return undefined;\n }\n const { sdpMid, sdpMLineIndex, usernameFragment } = value;\n if (\n !nullable(sdpMid, isCandidateText) ||\n !nullable(sdpMLineIndex, isLineIndex) ||\n !nullable(usernameFragment, isCandidateText)\n ) {\n return undefined;\n }\n return {\n candidate: value.candidate,\n ...(sdpMid === undefined ? {} : { sdpMid: sdpMid as string | null }),\n ...(sdpMLineIndex === undefined ? {} : { sdpMLineIndex: sdpMLineIndex as number | null }),\n ...(usernameFragment === undefined\n ? {}\n : { usernameFragment: usernameFragment as string | null }),\n };\n}\n\n/**\n * The signal `value` holds, copied, or `undefined` unless it holds exactly\n * one of `sdp`, `ice`, `relay: true` and `bye: true`, well formed. Fields\n * it does not know are left behind.\n */\nexport function readSignal(value: unknown): PeerSignal | undefined {\n if (!isRecord(value)) {\n return undefined;\n }\n const { sdp, ice, relay, bye } = value;\n if ([sdp, ice, relay, bye].filter((member) => member !== undefined).length !== 1) {\n return undefined;\n }\n if (sdp !== undefined) {\n const description = readDescription(sdp);\n return description === undefined ? undefined : { sdp: description };\n }\n if (ice !== undefined) {\n const candidate = readCandidate(ice);\n return candidate === undefined ? undefined : { ice: candidate };\n }\n if (relay === true) {\n return { relay: true };\n }\n return bye === true ? { bye: true } : undefined;\n}\n"],"mappings":";;;;;;;;;;AAYO,IAAM,uBAAuB;AAGpC,SAAS,OAAO,OAAiC;AAC/C,SAAO,OAAO,UAAU,YAAY,MAAM,UAAU;AACtD;AAGA,SAAS,gBAAgB,OAAiC;AACxD,SAAO,OAAO,UAAU,YAAY,MAAM,UAAU;AACtD;AAGO,SAAS,WAAW,OAAiC;AAC1D,SAAO,OAAO,UAAU,YAAY,UAAU,MAAM,MAAM,UAAU;AACtE;AAGO,SAAS,WAAW,OAAiC;AAC1D,SAAO,OAAO,UAAU,YAAY,UAAU,MAAM,MAAM,UAAU;AACtE;AAGO,SAAS,YAAY,OAAoC;AAC9D,SAAO,UAAU,SAAS,UAAU;AACtC;AAGA,SAAS,SAAS,OAAgB,OAA6C;AAC7E,SAAO,UAAU,UAAa,UAAU,QAAQ,MAAM,KAAK;AAC7D;AAEA,SAAS,YAAY,OAAyB;AAC5C,SAAO,OAAO,UAAU,YAAY,OAAO,cAAc,KAAK,KAAK,SAAS;AAC9E;AAEA,SAAS,gBAAgB,OAA6C;AACpE,MAAI,CAAC,SAAS,KAAK,KAAM,MAAM,SAAS,WAAW,MAAM,SAAS,UAAW;AAC3E,WAAO;AAAA,EACT;AACA,SAAO,OAAO,MAAM,GAAG,IAAI,EAAE,MAAM,MAAM,MAAM,KAAK,MAAM,IAAI,IAAI;AACpE;AAEA,SAAS,cAAc,OAA2C;AAChE,MAAI,CAAC,SAAS,KAAK,KAAK,CAAC,gBAAgB,MAAM,SAAS,GAAG;AACzD,WAAO;AAAA,EACT;AACA,QAAM,EAAE,QAAQ,eAAe,iBAAiB,IAAI;AACpD,MACE,CAAC,SAAS,QAAQ,eAAe,KACjC,CAAC,SAAS,eAAe,WAAW,KACpC,CAAC,SAAS,kBAAkB,eAAe,GAC3C;AACA,WAAO;AAAA,EACT;AACA,SAAO;AAAA,IACL,WAAW,MAAM;AAAA,IACjB,GAAI,WAAW,SAAY,CAAC,IAAI,EAAE,OAAgC;AAAA,IAClE,GAAI,kBAAkB,SAAY,CAAC,IAAI,EAAE,cAA8C;AAAA,IACvF,GAAI,qBAAqB,SACrB,CAAC,IACD,EAAE,iBAAoD;AAAA,EAC5D;AACF;AAOO,SAAS,WAAW,OAAwC;AACjE,MAAI,CAAC,SAAS,KAAK,GAAG;AACpB,WAAO;AAAA,EACT;AACA,QAAM,EAAE,KAAK,KAAK,OAAO,IAAI,IAAI;AACjC,MAAI,CAAC,KAAK,KAAK,OAAO,GAAG,EAAE,OAAO,CAAC,WAAW,WAAW,MAAS,EAAE,WAAW,GAAG;AAChF,WAAO;AAAA,EACT;AACA,MAAI,QAAQ,QAAW;AACrB,UAAM,cAAc,gBAAgB,GAAG;AACvC,WAAO,gBAAgB,SAAY,SAAY,EAAE,KAAK,YAAY;AAAA,EACpE;AACA,MAAI,QAAQ,QAAW;AACrB,UAAM,YAAY,cAAc,GAAG;AACnC,WAAO,cAAc,SAAY,SAAY,EAAE,KAAK,UAAU;AAAA,EAChE;AACA,MAAI,UAAU,MAAM;AAClB,WAAO,EAAE,OAAO,KAAK;AAAA,EACvB;AACA,SAAO,QAAQ,OAAO,EAAE,KAAK,KAAK,IAAI;AACxC;","names":[]}