@fitzzero/quickdraw-core 5.1.0 → 5.1.2

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 (107) hide show
  1. package/README.md +533 -180
  2. package/dist/{admin-DKwkIh5i.d.ts → admin-DWWrT1h6.d.ts} +2 -2
  3. package/dist/{adminSchemas-2DUA_hZ8.d.ts → adminSchemas-D4bFKsrb.d.ts} +6 -4
  4. package/dist/{auth-BhWqdyb-.d.ts → auth-DXiSJZaf.d.ts} +42 -16
  5. package/dist/{caller-CXTFswBO.d.ts → caller-BT-Mv4bD.d.ts} +117 -22
  6. package/dist/{chunk-RT6OILOI.js → chunk-4D2DOGAW.js} +2 -14
  7. package/dist/chunk-4D2DOGAW.js.map +1 -0
  8. package/dist/chunk-5CGO426C.js +15 -0
  9. package/dist/chunk-5CGO426C.js.map +1 -0
  10. package/dist/{chunk-A6DJBGOG.js → chunk-6K5BY47X.js} +4594 -3481
  11. package/dist/chunk-6K5BY47X.js.map +1 -0
  12. package/dist/{chunk-VJTFXHOK.js → chunk-753VJIUP.js} +13 -15
  13. package/dist/chunk-753VJIUP.js.map +1 -0
  14. package/dist/{chunk-JZBCLNDU.js → chunk-7QCUX4TK.js} +4 -4
  15. package/dist/chunk-7QCUX4TK.js.map +1 -0
  16. package/dist/chunk-AK23XSWD.js +16 -0
  17. package/dist/chunk-AK23XSWD.js.map +1 -0
  18. package/dist/{chunk-3674EIRT.js → chunk-AONAFOYU.js} +14 -5
  19. package/dist/{chunk-3674EIRT.js.map → chunk-AONAFOYU.js.map} +1 -1
  20. package/dist/chunk-DKE6QO26.js +136 -0
  21. package/dist/chunk-DKE6QO26.js.map +1 -0
  22. package/dist/{chunk-H23KEFBA.js → chunk-DUBU3BB3.js} +30 -1
  23. package/dist/chunk-DUBU3BB3.js.map +1 -0
  24. package/dist/{chunk-CDKG4TFA.js → chunk-GKH3LJ7N.js} +1 -1
  25. package/dist/chunk-GKH3LJ7N.js.map +1 -0
  26. package/dist/{chunk-KNDDCVQJ.js → chunk-GSIPUNJL.js} +49 -9
  27. package/dist/chunk-GSIPUNJL.js.map +1 -0
  28. package/dist/{chunk-DD4SKCNO.js → chunk-LCC7HNFP.js} +12 -1
  29. package/dist/chunk-LCC7HNFP.js.map +1 -0
  30. package/dist/{chunk-2PWMUVC7.js → chunk-MBH6IDSM.js} +480 -12
  31. package/dist/chunk-MBH6IDSM.js.map +1 -0
  32. package/dist/{chunk-2UELNHDD.js → chunk-NMXYENH4.js} +10 -10
  33. package/dist/{chunk-2UELNHDD.js.map → chunk-NMXYENH4.js.map} +1 -1
  34. package/dist/chunk-NO4M6PO5.js +101 -0
  35. package/dist/chunk-NO4M6PO5.js.map +1 -0
  36. package/dist/{chunk-TBMKJ26R.js → chunk-NW6U45HX.js} +3 -1
  37. package/dist/chunk-NW6U45HX.js.map +1 -0
  38. package/dist/{chunk-REX5YPHX.js → chunk-RJ5I2VO3.js} +74 -19
  39. package/dist/chunk-RJ5I2VO3.js.map +1 -0
  40. package/dist/{chunk-QEKVYG5F.js → chunk-RL4VKLBX.js} +1 -1
  41. package/dist/{chunk-QEKVYG5F.js.map → chunk-RL4VKLBX.js.map} +1 -1
  42. package/dist/chunk-XH2IZVLT.js +62 -0
  43. package/dist/chunk-XH2IZVLT.js.map +1 -0
  44. package/dist/{chunk-4DIQHASK.js → chunk-Z5ILULF2.js} +26 -7
  45. package/dist/chunk-Z5ILULF2.js.map +1 -0
  46. package/dist/cli/quickdraw-docs.js +5 -3
  47. package/dist/cli/quickdraw-docs.js.map +1 -1
  48. package/dist/client/index.d.ts +30 -18
  49. package/dist/client/index.js +8 -6
  50. package/dist/client/index.js.map +1 -1
  51. package/dist/{clientTypes-Bc-G8n-U.d.ts → clientTypes-hWcPUVS9.d.ts} +3 -3
  52. package/dist/{connection-Cr1BU07W.d.ts → connection-CgijdGpT.d.ts} +8 -4
  53. package/dist/{createServer-CP9eDHZZ.d.ts → createServer-Cp-Wk500.d.ts} +36 -14
  54. package/dist/{createTestApp-UKVGEmOf.d.ts → createTestApp-BB_PkqQ7.d.ts} +16 -10
  55. package/dist/{envelope-Cp3YcVxC.d.ts → envelope-DzWyIXjj.d.ts} +18 -4
  56. package/dist/{http-Djj2Inuu.d.ts → http-DotQ5bj8.d.ts} +68 -17
  57. package/dist/index.d.ts +7 -7
  58. package/dist/index.js +37 -13
  59. package/dist/index.js.map +1 -1
  60. package/dist/{infer-Teo1xpsd.d.ts → infer-BmoZsL9a.d.ts} +43 -10
  61. package/dist/logger-Yz93KzdV.d.ts +30 -0
  62. package/dist/{metrics-BdBu5o8G.d.ts → metrics-DW80Exve.d.ts} +2 -2
  63. package/dist/origins-kvpxnVcW.d.ts +8 -0
  64. package/dist/prisma/index.d.ts +5 -5
  65. package/dist/prisma/index.js +130 -67
  66. package/dist/prisma/index.js.map +1 -1
  67. package/dist/{rateLimit-ZMIZJUZF.js → rateLimit-5RKBFLUR.js} +2 -2
  68. package/dist/server/auth/index.d.ts +82 -33
  69. package/dist/server/auth/index.js +273 -268
  70. package/dist/server/auth/index.js.map +1 -1
  71. package/dist/server/express/index.d.ts +9 -2
  72. package/dist/server/express/index.js +1 -1
  73. package/dist/server/index.d.ts +147 -67
  74. package/dist/server/index.js +520 -381
  75. package/dist/server/index.js.map +1 -1
  76. package/dist/server/mcp/index.d.ts +63 -17
  77. package/dist/server/mcp/index.js +256 -54
  78. package/dist/server/mcp/index.js.map +1 -1
  79. package/dist/server/otel.d.ts +3 -3
  80. package/dist/{sharing-DVa8W-yR.d.ts → sharing-UDLn1049.d.ts} +3 -3
  81. package/dist/testing/client.d.ts +14 -14
  82. package/dist/testing/client.js +9 -7
  83. package/dist/testing/client.js.map +1 -1
  84. package/dist/testing/index.d.ts +12 -12
  85. package/dist/testing/index.js +21 -12
  86. package/dist/testing/index.js.map +1 -1
  87. package/dist/testing/mock.d.ts +5 -5
  88. package/dist/testing/mock.js +8 -6
  89. package/dist/{types-Cu0vmx7c.d.ts → types-CYliTP65.d.ts} +1 -1
  90. package/dist/utils/index.d.ts +1 -1
  91. package/package.json +1 -1
  92. package/dist/chunk-2PWMUVC7.js.map +0 -1
  93. package/dist/chunk-4DIQHASK.js.map +0 -1
  94. package/dist/chunk-A6DJBGOG.js.map +0 -1
  95. package/dist/chunk-CDKG4TFA.js.map +0 -1
  96. package/dist/chunk-DD4SKCNO.js.map +0 -1
  97. package/dist/chunk-H23KEFBA.js.map +0 -1
  98. package/dist/chunk-JZBCLNDU.js.map +0 -1
  99. package/dist/chunk-KNDDCVQJ.js.map +0 -1
  100. package/dist/chunk-REX5YPHX.js.map +0 -1
  101. package/dist/chunk-RT6OILOI.js.map +0 -1
  102. package/dist/chunk-TBMKJ26R.js.map +0 -1
  103. package/dist/chunk-VJTFXHOK.js.map +0 -1
  104. package/dist/chunk-YSWH776Y.js +0 -13
  105. package/dist/chunk-YSWH776Y.js.map +0 -1
  106. package/dist/logger-BY28acW-.d.ts +0 -16
  107. /package/dist/{rateLimit-ZMIZJUZF.js.map → rateLimit-5RKBFLUR.js.map} +0 -0
@@ -1,5 +1,5 @@
1
- import { e as StandardSchemaWithJSON, d as StandardSchemaV1, I as InferInput, c as InferOutput, X as RowSchema, Q as QueryDef, P as ProjectionList, p as MutationDef, J as AnyContract, ap as MethodName } from './infer-Teo1xpsd.js';
2
- import { d as ListSort, c as ListQuery, b as ListPage, D as AdminWritable, u as AdminListInput, v as AdminListQuery, y as AdminPage, r as AdminCreateInput, s as AdminCreateQuery, B as AdminUpdateInput, C as AdminUpdateQuery, w as AdminMetaInput, n as AdminServiceMeta, z as AdminSubscribers } from './adminSchemas-2DUA_hZ8.js';
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-BmoZsL9a.js';
2
+ import { d as ListSort, c as ListQuery, b as ListPage, D as AdminWritable, u as AdminListInput, v as AdminListQuery, y as AdminPage, r as AdminCreateInput, s as AdminCreateQuery, B as AdminUpdateInput, C as AdminUpdateQuery, w as AdminMetaInput, n as AdminServiceMeta, z as AdminSubscribers } from './adminSchemas-D4bFKsrb.js';
3
3
 
4
4
  /** A schema a kit generates: validation, and its JSON Schema. */
5
5
  type KitSchema<Input, Output = Input> = StandardSchemaWithJSON<Input, Output>;
@@ -1,4 +1,4 @@
1
- import { z as SortDirection, A as AccessLevel } from './infer-Teo1xpsd.js';
1
+ import { z as SortDirection, A as AccessLevel } from './infer-BmoZsL9a.js';
2
2
 
3
3
  /** `list`'s page size when the caller gives none. */
4
4
  declare const LIST_DEFAULT_LIMIT = 50;
@@ -111,9 +111,11 @@ interface AdminFieldConfig {
111
111
  readonly relationService?: string;
112
112
  /**
113
113
  * What the field holds, when the kit knows it: `"grants"` for a user's
114
- * service-wide grants (`serviceAccess`), which `admin.handlers(contract,
115
- * { grants: true })` shows. An admin screen with a grants editor of its own
116
- * finds that field by it, never by its name. Absent for any other field.
114
+ * service-wide grants (`serviceAccess`, or the column the server's
115
+ * `auth.serviceAccessSource` names, whatever its name), which
116
+ * `admin.handlers(contract, { grants: true })` shows. An admin screen with
117
+ * a grants editor of its own finds that field by it, never by its name.
118
+ * Absent for any other field.
117
119
  */
118
120
  readonly kind?: "grants";
119
121
  /**
@@ -1,7 +1,7 @@
1
1
  import { IncomingMessage, IncomingHttpHeaders } from 'node:http';
2
2
  import { Socket } from 'socket.io';
3
- import { A as AccessLevel } from './infer-Teo1xpsd.js';
4
- import { P as Principal, M as MaybePromise } from './types-Cu0vmx7c.js';
3
+ import { A as AccessLevel } from './infer-BmoZsL9a.js';
4
+ import { P as Principal, M as MaybePromise } from './types-CYliTP65.js';
5
5
 
6
6
  /**
7
7
  * A request as Express leaves it: maybe with `body` from `express.json()`,
@@ -13,10 +13,13 @@ type HttpRequest = IncomingMessage & {
13
13
  readonly secure?: unknown;
14
14
  };
15
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.
16
+ * The credential the HTTP transport chose for a call (the cookie first).
17
+ * `"cookie"`: the session cookie, which a browser sends with a request any
18
+ * page makes, so whoever accepts it checks the request's `Origin`
19
+ * (`socketAuth` does). `"bearer"`: an `Authorization: Bearer` header, which a
20
+ * page sends only on purpose. For an `authenticate` `socketAuth` did not
21
+ * make, `auth.token` holds the bearer token alone, even when this says
22
+ * `"cookie"`; the cookie is `request.sessionCookie`.
20
23
  */
21
24
  type HttpCredentialSource = "cookie" | "bearer";
22
25
 
@@ -26,7 +29,10 @@ interface AuthenticateRequestBase {
26
29
  /**
27
30
  * The credentials. For a socket, its handshake `auth` as the client sent
28
31
  * it (`{ token, qd }` from a v5 client). For an HTTP request, `{ token }`
29
- * with its session cookie or bearer token, or `{}` when it has neither.
32
+ * with its bearer token, or `{}` without one; for `socketAuth`'s
33
+ * `authenticate` alone, the session cookie when the request carries one,
34
+ * else the bearer token. Every other `authenticate` finds the cookie only
35
+ * in `sessionCookie`.
30
36
  */
31
37
  readonly auth: Readonly<Record<string, unknown>>;
32
38
  /** The request's headers, or the socket handshake's. */
@@ -42,13 +48,23 @@ interface HttpAuthenticateRequest extends AuthenticateRequestBase {
42
48
  readonly transport: "http";
43
49
  readonly req: IncomingMessage;
44
50
  /**
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.
51
+ * The credential the transport chose (finding F7.1): `"cookie"` when the
52
+ * request carries the session cookie, which a browser sends with a
53
+ * request any page makes, so an `authenticate` that accepts it checks the
54
+ * request's `Origin` as `socketAuth` does; else `"bearer"` for an
55
+ * `Authorization: Bearer` header, which a page sends only on purpose.
56
+ * Absent with neither.
50
57
  */
51
58
  readonly credential?: HttpCredentialSource;
59
+ /**
60
+ * The session cookie's value, when the request carries one. It is
61
+ * ambient (a browser sends it with a request any page makes), so an
62
+ * `authenticate` that accepts it first checks
63
+ * `cookieOriginAllowed(request, allowedOrigins)`, then the session store,
64
+ * on every call (`liveSession`). It is in `auth.token` only for
65
+ * `socketAuth`'s `authenticate`.
66
+ */
67
+ readonly sessionCookie?: string;
52
68
  }
53
69
  /** What `authenticate` is asked about: a socket connecting, or an HTTP call. */
54
70
  type AuthenticateRequest = SocketAuthenticateRequest | HttpAuthenticateRequest;
@@ -87,7 +103,13 @@ interface ServerAuth<P extends Principal = Principal> {
87
103
  * sets that column, or creates, deletes or touches such a row, refreshes
88
104
  * that user's grants once it is flushed (`server.access.refresh`), so a
89
105
  * changed grant reaches their sockets without a reconnect (RFC 0003
90
- * section 4.4). Needs `loadServiceAccess`.
106
+ * section 4.4). `createServer` gives it to its dispatcher, whose admin kit
107
+ * on that model treats the column as the grants field, whatever its name:
108
+ * hidden unless `grants: true`, and then for service-wide Admins only. A
109
+ * dispatcher made without `createServer` takes it as
110
+ * `createDispatcher({ serviceAccessSource })`, and `createHttpRouter`
111
+ * refuses an `auth.serviceAccessSource` its dispatcher does not name.
112
+ * Needs `loadServiceAccess`.
91
113
  */
92
114
  readonly serviceAccessSource?: ServiceAccessSource;
93
115
  }
@@ -101,9 +123,13 @@ interface ServiceAccessSource {
101
123
  /**
102
124
  * Records the session a socket authenticated with, so that
103
125
  * `server.access.disconnectUser(userId, { sessionId })` can end that
104
- * session's sockets alone. `socketAuth` calls it for the auth routes'
105
- * sessions; an app's own `authenticate` may too.
126
+ * session's sockets alone, and, with `expiresAt`, so that the server
127
+ * disconnects the socket when the session expires (the 5.1 audit's AUTH-4)
128
+ * and refuses its handshake when it already has. `expiresAt` is a `Date`, a
129
+ * date string, or epoch milliseconds: a JWT's `exp` is in seconds, so pass
130
+ * `exp * 1000`. `socketAuth` calls it for the auth routes' sessions; an
131
+ * app's own `authenticate` may too.
106
132
  */
107
- declare function recordSocketSession(socket: object, sessionId: string): void;
133
+ declare function recordSocketSession(socket: object, sessionId: string, expiresAt?: Date | number | string): void;
108
134
 
109
135
  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-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 { ad as Version, a5 as Revision } from './envelope-DzWyIXjj.js';
2
+ import { L as Logger } from './logger-Yz93KzdV.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-BmoZsL9a.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-CYliTP65.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;
@@ -389,6 +438,14 @@ interface ServiceMethod {
389
438
  * caller is left to the form.
390
439
  */
391
440
  readonly kinds: readonly string[] | undefined;
441
+ /**
442
+ * The method's requirement on the caller's claims (RFC 0003 section 4.1,
443
+ * `access/claims.ts`), checked right after `kinds`, outside the access
444
+ * engine: `UNAUTHENTICATED` without a principal, `FORBIDDEN` unless it
445
+ * returns `true`. It adds to the access form, and no grant passes it, a
446
+ * service-wide `Admin` grant included. `undefined` when not declared.
447
+ */
448
+ readonly claims: ClaimsCheck | undefined;
392
449
  readonly handler: AnyHandler;
393
450
  readonly share: ShareMode | undefined;
394
451
  /** How long a shared result is reused after its run, in milliseconds. */
@@ -524,6 +581,11 @@ interface ScopeAccess<Input = never> {
524
581
  readonly service?: never;
525
582
  readonly kind?: never;
526
583
  }
584
+ /**
585
+ * A method's `claims`, as the pipeline calls it (`claims.ts`): `true` when
586
+ * the caller's claims allow the call with `input`.
587
+ */
588
+ type ClaimsCheck = (claims: Principal["claims"], input: unknown) => boolean;
527
589
  /**
528
590
  * `custom(fn)`: the call passes when `fn(ctx, input)` resolves `true`. The
529
591
  * caller must be authenticated before `fn` runs, so `ctx.principal` is never
@@ -602,7 +664,8 @@ interface RowAccess {
602
664
  * Who is online and who is in a room (RFC 0003 section 12.5): `ctx.presence`,
603
665
  * `server.presence`, `qd.presence`. Every answer is a promise: on a server
604
666
  * 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()`).
667
+ * cluster adapter (Redis) it asks the other nodes too, which answer with
668
+ * user ids (or whether they have a socket of the user) alone.
606
669
  */
607
670
  interface Presence {
608
671
  /** True while the user has a socket connected (on any node). */
@@ -614,9 +677,14 @@ interface Presence {
614
677
  * the 100,000 users seen last).
615
678
  */
616
679
  lastSeen(userId: string): Promise<number | null>;
617
- /** How many users have a socket in `room`. Anonymous sockets do not count. */
680
+ /**
681
+ * How many users have a socket in app room `room` that `ctx.rooms.join`
682
+ * put there (a raw `socket.join` does not count on this process).
683
+ * Anonymous sockets do not count. A framework room (`qd:`, `user:`) or a
684
+ * name with the shape of a socket id is `VALIDATION`.
685
+ */
618
686
  count(room: string): Promise<number>;
619
- /** The ids of the users with a socket in `room`, each once. */
687
+ /** The ids of the users `count` counts in `room`, each once. */
620
688
  users(room: string): Promise<string[]>;
621
689
  }
622
690
  /** Whose sockets `rooms.leave(room, target)` takes out of a room: every socket of one user. */
@@ -639,8 +707,9 @@ interface ServerRooms {
639
707
  /**
640
708
  * Sends the contract's event to every socket in `room` as `qd:event`, on
641
709
  * 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.
710
+ * payload that fails it throws `INTERNAL` and nothing is sent. A room of
711
+ * the framework's (`qd:`, `user:`) throws `VALIDATION`: send to a user's
712
+ * sockets with `emitToUser`. Without a server it does nothing more.
644
713
  */
645
714
  emit<C extends AnyContract, E extends EventName<C>>(room: string, contract: C, event: E, payload: EventPayloadOf<C, E>): void;
646
715
  /** `emit` to every socket of one user (their `user:{userId}` room), on every node. */
@@ -685,8 +754,10 @@ interface ContextRooms extends ServerRooms {
685
754
  * nothing, for a call that did not arrive over a socket (HTTP, MCP, in
686
755
  * process). Room names starting with `qd:` or `user:` are refused with
687
756
  * `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`.
757
+ * paths), as are names with the shape of a Socket.IO socket id (20
758
+ * letters, digits, `_` or `-`: every socket is alone in the room of its
759
+ * id), empty names and names over 256 characters; a socket in 100 app
760
+ * rooms is refused another with `CONFLICT`.
690
761
  */
691
762
  join(room: string): boolean;
692
763
  /** Takes the calling socket out of an app room; `false` when it was not in it, or the call has no socket. */
@@ -850,7 +921,12 @@ interface ChannelContext<P = Principal, Room extends string | undefined = string
850
921
  type ChannelHandler<T extends QuickdrawTypes, Payload, Room extends string | undefined = string | undefined> = (payload: Payload, ctx: ChannelContext<PrincipalOf<T>, Room>) => void | PromiseLike<void>;
851
922
  /** One channel's implementation in `defineService`: its handler, or `{ access, handler }`. */
852
923
  type ChannelImplementation<T extends QuickdrawTypes, Payload, Room extends string | undefined = string | undefined> = ChannelHandler<T, Payload, Room> | {
853
- /** Default `"authenticated"`. */
924
+ /**
925
+ * Default `"authenticated"`. A channel whose contract declares no
926
+ * `requires` takes a message from every signed-in socket: declaring
927
+ * `access` (even `"authenticated"`) says so on purpose, and silences
928
+ * the `channel-without-requires` warning the server raises otherwise.
929
+ */
854
930
  readonly access?: ChannelAccess;
855
931
  readonly handler: ChannelHandler<T, Payload, Room>;
856
932
  };
@@ -920,6 +996,12 @@ interface ServiceChannel {
920
996
  readonly prefix: string;
921
997
  } | undefined;
922
998
  readonly access: ChannelAccess;
999
+ /**
1000
+ * True when the service named `access` for the channel (`{ access,
1001
+ * handler }`), not a bare handler or `{ handler }`: a channel without
1002
+ * `requires` warns `channel-without-requires` unless it did.
1003
+ */
1004
+ readonly accessDeclared: boolean;
923
1005
  /**
924
1006
  * The kinds of principal the channel's service admits, as a set for the
925
1007
  * per-message check: a message from a socket of another kind, or without
@@ -1112,19 +1194,32 @@ interface WriteRecord {
1112
1194
  /** The written row's id. */
1113
1195
  readonly id: string;
1114
1196
  readonly op: "create" | "update" | "delete";
1115
- /** The fields the write set; `[ANY_FIELD]` when they are unknown. */
1197
+ /**
1198
+ * The fields the write set; `[ANY_FIELD]` when they are unknown. An update
1199
+ * whose data held a nested write, which may set a foreign key through a
1200
+ * relation, lists every interested column of the model besides its keys.
1201
+ */
1116
1202
  readonly fields: readonly string[];
1117
1203
  /**
1118
1204
  * The row before the unit of work wrote it, for its interested columns: an
1119
1205
  * 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.
1206
+ * narrowly), or of every interested column when its data held a nested
1207
+ * write, or a delete's values. A row the unit created has none.
1121
1208
  */
1122
1209
  readonly before?: Readonly<Record<string, unknown>>;
1123
1210
  /** The interested columns' values after the write, when the write returned them. */
1124
1211
  readonly after?: Readonly<Record<string, unknown>>;
1212
+ /**
1213
+ * On a delete of a model whose junction links are registered
1214
+ * (`StorageAdapter.registerLinks`): per junction model, the junction rows
1215
+ * that linked the row just before the delete (possibly none), with the
1216
+ * registered columns. Absent when the links were not read.
1217
+ */
1218
+ readonly links?: Readonly<Record<string, readonly Readonly<Record<string, unknown>>[]>>;
1125
1219
  /**
1126
1220
  * 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
1221
+ * existed (its `update` set no interested column and held no nested
1222
+ * write, or the model has no interested columns): the row may have been
1128
1223
  * updated rather than created. Only merging reads it: a row created and
1129
1224
  * deleted in one unit is dropped from the flush only when its create is
1130
1225
  * certain.
@@ -1248,4 +1343,4 @@ type CallerFor<T extends QuickdrawTypes> = T extends {
1248
1343
  readonly contracts: infer Contracts extends ContractMap;
1249
1344
  } ? Caller<Contracts[keyof Contracts]> : Caller<AnyContract>;
1250
1345
 
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 };
1346
+ 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, type LinkInterest as aL, formatDevWarning as aM, N_PLUS_ONE_STATEMENTS as aN, 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 };
@@ -11,21 +11,9 @@ function via(junction) {
11
11
  });
12
12
  }
13
13
 
14
- // src/contract/realtime.ts
15
- var GLOBAL_STREAM = "global";
16
- var STREAM_MAX_SEED = 1e3;
17
- var CHANNEL_DEFAULT_RATE = 30;
18
- function isScopedStream(def) {
19
- return def.scope !== void 0 && def.scope !== GLOBAL_STREAM;
20
- }
21
-
22
14
  export {
23
15
  DEFAULT_COLLECTION_LIMIT,
24
16
  DEFAULT_COLLECTION_MAX_LIMIT,
25
- via,
26
- GLOBAL_STREAM,
27
- STREAM_MAX_SEED,
28
- CHANNEL_DEFAULT_RATE,
29
- isScopedStream
17
+ via
30
18
  };
31
- //# sourceMappingURL=chunk-RT6OILOI.js.map
19
+ //# sourceMappingURL=chunk-4D2DOGAW.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/contract/collections.ts"],"sourcesContent":["// Collection declarations for a contract (RFC 0003 section 7.1). A collection\n// is the rows of one service grouped by a scope value. Membership is declared\n// (a scope column or a junction table, plus an equality filter), never\n// computed by a function, so the framework knows which writes can move a row.\n// This replaces 4.1's `defineCollection({ resolveScopeId, checkScopeAccess,\n// snapshot, toItem })`.\n\nimport type { AccessLevel } from \"./access\";\n\n/** Page size of a collection snapshot when the collection sets no `limit`. */\nexport const DEFAULT_COLLECTION_LIMIT = 100;\n\n/** Largest page a client may ask for when the collection sets no `maxLimit`. */\nexport const DEFAULT_COLLECTION_MAX_LIMIT = 500;\n\n/**\n * A scope that comes from a junction table instead of a column on the row:\n * the rows of `model` link an entry (`entry` holds the row's id) to a scope\n * value (`scope`). For example, a chat appears in each member's list through\n * `via({ model: \"chatMember\", entry: \"chatId\", scope: \"userId\" })`.\n */\nexport interface ViaScope<\n Model extends string = string,\n Entry extends string = string,\n Scope extends string = string,\n> {\n readonly kind: \"via\";\n readonly model: Model;\n readonly entry: Entry;\n readonly scope: Scope;\n /**\n * The item is computed from the junction (a member count): every junction\n * write sends its entry again, whole, to each scope that still holds it.\n * Without it a junction write only adds or removes the entry in the scope\n * it links, so a count over the junction goes stale everywhere else.\n */\n readonly refreshEntry?: boolean;\n}\n\n/** What `via(...)` takes: the junction model, its entry and scope columns, and `refreshEntry`. */\nexport interface ViaOptions<\n Model extends string = string,\n Entry extends string = string,\n Scope extends string = string,\n> {\n /**\n * The junction model, as the database client names it: `\"chatMember\"`. It\n * needs an `id` column (keep the pair unique with `@@unique`, never a\n * composite `@@id`), or its writes are not tracked and never move a scope.\n */\n readonly model: Model;\n /** The junction column holding the entry's id: `\"chatId\"`. */\n readonly entry: Entry;\n /** The junction column holding the scope value: `\"userId\"`. */\n readonly scope: Scope;\n /**\n * Set when the item reads the junction, as a `memberCount` mapped from a\n * relation count does: a junction create, update or delete then sends the\n * entry again, as `updated`, to every scope that still holds it (one read\n * of the entry's item per flush). Default `false`: a junction write adds\n * or removes the entry for the scope it links, and nothing else.\n */\n readonly refreshEntry?: boolean;\n}\n\n/**\n * Declares a collection scope that comes from a junction table.\n *\n * @example\n * myChats: {\n * scope: via({ model: \"chatMember\", entry: \"chatId\", scope: \"userId\", refreshEntry: true }),\n * item: \"listItem\",\n * order: [[\"id\", \"asc\"]],\n * }\n */\nexport function via<\n const Model extends string,\n const Entry extends string,\n const Scope extends string,\n>(junction: ViaOptions<Model, Entry, Scope>): ViaScope<Model, Entry, Scope> {\n return Object.freeze({\n kind: \"via\",\n model: junction.model,\n entry: junction.entry,\n scope: junction.scope,\n ...(junction.refreshEntry === undefined ? {} : { refreshEntry: junction.refreshEntry }),\n });\n}\n\nexport type SortDirection = \"asc\" | \"desc\";\n\n/**\n * Sort columns for the keyset cursor, ending in `id` so every row has a\n * unique position: `[[\"ordinal\", \"asc\"], [\"id\", \"asc\"]]`.\n */\nexport type OrderBy<Column extends string = string> = readonly [\n ...(readonly [Column, SortDirection])[],\n readonly [\"id\", SortDirection],\n];\n\n/** Who a view is evaluated for, on the client. */\nexport interface Viewer {\n readonly userId: string;\n}\n\n/**\n * A view: a named pure predicate evaluated on the client over the index rows\n * of a scope (RFC 0003 section 7.5). `row` holds only `id` and the\n * collection's `index` fields, which the type enforces.\n */\nexport type ViewPredicate<Row> = (row: Row, who: Viewer) => boolean;\n\n/** The equality filter that decides membership, for example `{ archived: false }`. */\nexport interface CollectionWhere {\n readonly [column: string]: string | number | boolean | null;\n}\n\n/** Any collection of any contract, as `defineContract` stores it. */\nexport interface CollectionDef {\n /** What the list holds, in a sentence or two, for the generated docs. */\n readonly describe?: string | undefined;\n /** A string column of the entity, or `via(...)` for a junction table. */\n readonly scope: string | ViaScope;\n /** The projection each item is sent as: `\"entity\"` or a named projection. */\n readonly item: string;\n /** Sort columns for the keyset cursor; the last one is `id`. */\n readonly order: OrderBy;\n /** Equality filter that decides membership. */\n readonly where?: CollectionWhere | undefined;\n /** Page size. Default {@link DEFAULT_COLLECTION_LIMIT}. */\n readonly limit?: number | undefined;\n /** Largest page a client may ask for. Default {@link DEFAULT_COLLECTION_MAX_LIMIT}. */\n readonly maxLimit?: number | undefined;\n /** Item fields sent for every member of the scope (RFC 0003 section 7.4). */\n readonly index?: readonly string[] | undefined;\n /** Named predicates over index rows. Requires `index`. */\n readonly views?: { readonly [view: string]: ViewPredicate<never> } | undefined;\n /** Level required on the scope's anchor row. Default `\"Read\"`, through the anchor's policy. */\n readonly access?: AccessLevel | undefined;\n}\n"],"mappings":";AAUO,IAAM,2BAA2B;AAGjC,IAAM,+BAA+B;AA8DrC,SAAS,IAId,UAA0E;AAC1E,SAAO,OAAO,OAAO;AAAA,IACnB,MAAM;AAAA,IACN,OAAO,SAAS;AAAA,IAChB,OAAO,SAAS;AAAA,IAChB,OAAO,SAAS;AAAA,IAChB,GAAI,SAAS,iBAAiB,SAAY,CAAC,IAAI,EAAE,cAAc,SAAS,aAAa;AAAA,EACvF,CAAC;AACH;","names":[]}
@@ -0,0 +1,15 @@
1
+ // src/contract/realtime.ts
2
+ var GLOBAL_STREAM = "global";
3
+ var STREAM_MAX_SEED = 1e3;
4
+ var CHANNEL_DEFAULT_RATE = 30;
5
+ function isScopedStream(def) {
6
+ return def.scope !== void 0 && def.scope !== GLOBAL_STREAM;
7
+ }
8
+
9
+ export {
10
+ GLOBAL_STREAM,
11
+ STREAM_MAX_SEED,
12
+ CHANNEL_DEFAULT_RATE,
13
+ isScopedStream
14
+ };
15
+ //# sourceMappingURL=chunk-5CGO426C.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/contract/realtime.ts"],"sourcesContent":["// Streams, channels and typed room events in a contract (RFC 0003 section\n// 12.5). A stream is server-to-client append-only data a subscriber receives\n// with a seed of recent items; a channel is fire-and-forget client-to-server\n// input (cursors, typing) behind a per-socket token bucket; an event is a\n// typed custom frame a handler sends to a room. 4.1 declared channels on the\n// service class (4.1 `src/server/BaseService.ts:915-942`) and typed room\n// events through the augmentable `QuickdrawEventMap`; here all three live in\n// the contract, so the client is typed from it.\n//\n// Methods, collections, streams, channels and events share one namespace:\n// the client exposes each as `qd.<service>.<name>`.\n\nimport type { AccessLevel } from \"./access\";\nimport type { AnyContract } from \"./defineContract\";\nimport type { StandardSchemaV1 } from \"./standardSchema\";\n\n/** The `scope` of a stream with one feed for the whole service: the default. */\nexport const GLOBAL_STREAM = \"global\";\n\n/** The most items a stream may keep per scope as the seed for new subscribers. */\nexport const STREAM_MAX_SEED = 1000;\n\n/** A channel's sustained rate when it declares none: messages per second per socket. */\nexport const CHANNEL_DEFAULT_RATE = 30;\n\n/**\n * The app room a stream's subscriber must be in (`access: { room }`): for a\n * global stream the room's name, or `{ prefix }` for any room whose name\n * starts with it; for a scoped stream a function of the scope\n * (`(worldId) => \\`world:${worldId}\\``), since a name or a prefix would let\n * a socket in that room read every scope (`defineContract` refuses them\n * there).\n */\nexport type StreamRoom = string | RoomPrefix | ((scope: string) => string | null | undefined);\n\n/**\n * Who may subscribe to a stream (RFC 0003 section 4.1's forms, as data). A\n * scoped stream's scope value is the row an `entry` or `scope` form is about:\n * `{ entry: L }` needs level `L` on the row of this service whose id is the\n * scope, `{ scope: L, of }` level `L` on that row of `of`'s service.\n * `{ room }` opens it to the sockets in an app room a method joined them to\n * (signed in or not), as a channel's `requires: { room }` does its messages:\n * a socket that leaves the room, or is taken out of it, leaves the feed too.\n * A scoped stream computes that room from its scope; a name or `{ prefix }`\n * serves a global stream only. A stream that declares none is closed: no\n * client may subscribe.\n */\nexport type StreamAccess =\n | \"public\"\n | \"authenticated\"\n | {\n readonly service: AccessLevel;\n readonly entry?: undefined;\n readonly scope?: undefined;\n readonly of?: undefined;\n readonly room?: undefined;\n }\n | {\n readonly entry: AccessLevel;\n readonly service?: AccessLevel;\n readonly scope?: undefined;\n readonly of?: undefined;\n readonly room?: undefined;\n }\n | {\n readonly scope: AccessLevel;\n readonly of: AnyContract;\n readonly service?: undefined;\n readonly entry?: undefined;\n readonly room?: undefined;\n }\n | {\n readonly room: StreamRoom;\n readonly service?: undefined;\n readonly entry?: undefined;\n readonly scope?: undefined;\n readonly of?: undefined;\n };\n\n/** A server-to-client stream of append-only items (RFC 0003 section 12.5). */\nexport interface StreamDef<Item extends StandardSchemaV1 = StandardSchemaV1> {\n /** One item's schema. Pushed items are checked against it before they are sent. */\n readonly item: Item;\n /**\n * What a scope value is (`\"taskId\"`): the stream has one feed per scope.\n * `\"global\"`, the default, makes one feed for the whole service.\n */\n readonly scope?: string;\n /**\n * How many of the latest items the server keeps per scope, in memory on\n * each process, and sends a new subscriber first. Default 0; at most 1,000.\n */\n readonly seed?: number;\n /** Send items volatile: dropped for a client whose connection is backed up. Default `false`. */\n readonly volatile?: boolean;\n /** Who may subscribe. Without it the stream is closed. */\n readonly access?: StreamAccess;\n /** What the stream carries, in a sentence or two, for the generated docs. */\n readonly describe?: string;\n}\n\n/**\n * Where a channel requirement finds its row id or scope value in the parsed\n * payload: the name of a key holding a string, or a function of the payload.\n */\nexport type PayloadSelector = string | ((payload: never) => string | null | undefined);\n\n/**\n * Any app room whose name starts with `prefix` (`{ prefix: \"world:\" }`, a\n * game of many worlds): the sending socket passes when it is in one, and\n * the channel's handler gets the one it matched as `ctx.room`, so the\n * payload need not repeat which room it is for.\n */\nexport interface RoomPrefix {\n readonly prefix: string;\n}\n\n/**\n * The app room a channel requirement names: the room itself (`\"world\"`, a\n * game's one world), a function of the parsed payload that returns it\n * (`(payload) => \\`lobby:${payload.lobbyId}\\``), or `{ prefix }`, any room\n * whose name starts with it (`{ prefix: \"world:\" }`). Unlike a\n * {@link PayloadSelector}, a string here is the room's name, not a payload\n * key. A name starting with `qd:` or `user:`, or one with the shape of a\n * Socket.IO socket id (20 letters, digits, `_` or `-`), is never an app\n * room: a literal one (or such a prefix) is refused when the contract is\n * defined, and a computed one drops the message.\n */\nexport type RoomSelector = string | ((payload: never) => string | null | undefined) | RoomPrefix;\n\n/**\n * What a channel message requires of the socket that sends it: a live\n * subscription (`qd:sub`) to the row of this service `entity` names, or\n * (`qd:col:sub`) to the scope of `collection` that `scope` names; or that\n * the socket is in the app room `room` names (or, for `{ prefix }`, in any\n * app room whose name starts with it), which a method called over that\n * same socket joined with `ctx.rooms.join` (a room another socket of the\n * user joined does not count, and a reconnected socket is in none until it\n * joins again). The handler gets the room that matched as `ctx.room`. Each\n * is checked in memory against the sending socket's own records, on the\n * node it is connected to. A message whose payload names none is dropped.\n */\nexport type ChannelRequires =\n | {\n readonly entity: PayloadSelector;\n readonly collection?: undefined;\n readonly scope?: undefined;\n readonly room?: undefined;\n }\n | {\n readonly collection: string;\n readonly scope: PayloadSelector;\n readonly entity?: undefined;\n readonly room?: undefined;\n }\n | {\n readonly room: RoomSelector;\n readonly entity?: undefined;\n readonly collection?: undefined;\n readonly scope?: undefined;\n };\n\n/** A client-to-server fire-and-forget channel (RFC 0003 section 12.5). */\nexport interface ChannelDef<Payload extends StandardSchemaV1 = StandardSchemaV1> {\n /** One message's schema. A message that fails it is dropped. */\n readonly payload: Payload;\n /** Messages one socket may send per second, sustained. Default 30. */\n readonly ratePerSecond?: number;\n /** How many messages one socket may send at once. Default twice `ratePerSecond`. */\n readonly burst?: number;\n /**\n * What the sender must already be subscribed to, or the app room it must\n * be in. Without it every signed-in user may send the channel, about any\n * row or room its payload names, and the server warns\n * `channel-without-requires` unless the service declares the channel's\n * `access` (`\"authenticated\"` when any signed-in user may send it).\n */\n readonly requires?: ChannelRequires;\n /** What a message says, in a sentence or two, for the generated docs. */\n readonly describe?: string;\n}\n\n/** A typed custom room event, delivered as `qd:event` (RFC 0003 section 8.3). */\nexport interface EventDef<Payload extends StandardSchemaV1 = StandardSchemaV1> {\n readonly payload: Payload;\n /** What the event announces, in a sentence or two, for the generated docs. */\n readonly describe?: string;\n}\n\n/** True when a stream has one feed per scope value; false for a global stream. */\nexport function isScopedStream(def: Pick<StreamDef, \"scope\">): boolean {\n return def.scope !== undefined && def.scope !== GLOBAL_STREAM;\n}\n"],"mappings":";AAiBO,IAAM,gBAAgB;AAGtB,IAAM,kBAAkB;AAGxB,IAAM,uBAAuB;AAuK7B,SAAS,eAAe,KAAwC;AACrE,SAAO,IAAI,UAAU,UAAa,IAAI,UAAU;AAClD;","names":[]}