@fitzzero/quickdraw-core 5.1.1 → 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.
- package/README.md +533 -180
- package/dist/{admin-DKwkIh5i.d.ts → admin-DWWrT1h6.d.ts} +2 -2
- package/dist/{adminSchemas-2DUA_hZ8.d.ts → adminSchemas-D4bFKsrb.d.ts} +6 -4
- package/dist/{auth-BhWqdyb-.d.ts → auth-DXiSJZaf.d.ts} +42 -16
- package/dist/{caller-CXTFswBO.d.ts → caller-BT-Mv4bD.d.ts} +117 -22
- package/dist/{chunk-RT6OILOI.js → chunk-4D2DOGAW.js} +2 -14
- package/dist/chunk-4D2DOGAW.js.map +1 -0
- package/dist/chunk-5CGO426C.js +15 -0
- package/dist/chunk-5CGO426C.js.map +1 -0
- package/dist/{chunk-RZTP4IEW.js → chunk-6K5BY47X.js} +4594 -3481
- package/dist/chunk-6K5BY47X.js.map +1 -0
- package/dist/{chunk-VSQBHPWH.js → chunk-753VJIUP.js} +13 -15
- package/dist/chunk-753VJIUP.js.map +1 -0
- package/dist/{chunk-ZIVPHYRG.js → chunk-7QCUX4TK.js} +4 -4
- package/dist/chunk-7QCUX4TK.js.map +1 -0
- package/dist/chunk-AK23XSWD.js +16 -0
- package/dist/chunk-AK23XSWD.js.map +1 -0
- package/dist/{chunk-SHEGLZEP.js → chunk-AONAFOYU.js} +14 -5
- package/dist/{chunk-SHEGLZEP.js.map → chunk-AONAFOYU.js.map} +1 -1
- package/dist/chunk-DKE6QO26.js +136 -0
- package/dist/chunk-DKE6QO26.js.map +1 -0
- package/dist/{chunk-H23KEFBA.js → chunk-DUBU3BB3.js} +30 -1
- package/dist/chunk-DUBU3BB3.js.map +1 -0
- package/dist/{chunk-CDKG4TFA.js → chunk-GKH3LJ7N.js} +1 -1
- package/dist/chunk-GKH3LJ7N.js.map +1 -0
- package/dist/{chunk-OECZPRPW.js → chunk-GSIPUNJL.js} +49 -9
- package/dist/chunk-GSIPUNJL.js.map +1 -0
- package/dist/{chunk-DD4SKCNO.js → chunk-LCC7HNFP.js} +12 -1
- package/dist/chunk-LCC7HNFP.js.map +1 -0
- package/dist/{chunk-2PWMUVC7.js → chunk-MBH6IDSM.js} +480 -12
- package/dist/chunk-MBH6IDSM.js.map +1 -0
- package/dist/{chunk-QCHKWIRS.js → chunk-NMXYENH4.js} +10 -10
- package/dist/{chunk-QCHKWIRS.js.map → chunk-NMXYENH4.js.map} +1 -1
- package/dist/chunk-NO4M6PO5.js +101 -0
- package/dist/chunk-NO4M6PO5.js.map +1 -0
- package/dist/{chunk-TBMKJ26R.js → chunk-NW6U45HX.js} +3 -1
- package/dist/chunk-NW6U45HX.js.map +1 -0
- package/dist/{chunk-REX5YPHX.js → chunk-RJ5I2VO3.js} +74 -19
- package/dist/chunk-RJ5I2VO3.js.map +1 -0
- package/dist/{chunk-QEKVYG5F.js → chunk-RL4VKLBX.js} +1 -1
- package/dist/{chunk-QEKVYG5F.js.map → chunk-RL4VKLBX.js.map} +1 -1
- package/dist/chunk-XH2IZVLT.js +62 -0
- package/dist/chunk-XH2IZVLT.js.map +1 -0
- package/dist/{chunk-4DIQHASK.js → chunk-Z5ILULF2.js} +26 -7
- package/dist/chunk-Z5ILULF2.js.map +1 -0
- package/dist/cli/quickdraw-docs.js +5 -3
- package/dist/cli/quickdraw-docs.js.map +1 -1
- package/dist/client/index.d.ts +30 -18
- package/dist/client/index.js +8 -6
- package/dist/client/index.js.map +1 -1
- package/dist/{clientTypes-Bc-G8n-U.d.ts → clientTypes-hWcPUVS9.d.ts} +3 -3
- package/dist/{connection-Cr1BU07W.d.ts → connection-CgijdGpT.d.ts} +8 -4
- package/dist/{createServer-CP9eDHZZ.d.ts → createServer-Cp-Wk500.d.ts} +36 -14
- package/dist/{createTestApp-UKVGEmOf.d.ts → createTestApp-BB_PkqQ7.d.ts} +16 -10
- package/dist/{envelope-Cp3YcVxC.d.ts → envelope-DzWyIXjj.d.ts} +18 -4
- package/dist/{http-Djj2Inuu.d.ts → http-DotQ5bj8.d.ts} +68 -17
- package/dist/index.d.ts +7 -7
- package/dist/index.js +37 -13
- package/dist/index.js.map +1 -1
- package/dist/{infer-Teo1xpsd.d.ts → infer-BmoZsL9a.d.ts} +43 -10
- package/dist/logger-Yz93KzdV.d.ts +30 -0
- package/dist/{metrics-BdBu5o8G.d.ts → metrics-DW80Exve.d.ts} +2 -2
- package/dist/origins-kvpxnVcW.d.ts +8 -0
- package/dist/prisma/index.d.ts +5 -5
- package/dist/prisma/index.js +130 -67
- package/dist/prisma/index.js.map +1 -1
- package/dist/{rateLimit-ZMIZJUZF.js → rateLimit-5RKBFLUR.js} +2 -2
- package/dist/server/auth/index.d.ts +82 -33
- package/dist/server/auth/index.js +273 -268
- package/dist/server/auth/index.js.map +1 -1
- package/dist/server/express/index.d.ts +9 -2
- package/dist/server/express/index.js +1 -1
- package/dist/server/index.d.ts +147 -67
- package/dist/server/index.js +520 -381
- package/dist/server/index.js.map +1 -1
- package/dist/server/mcp/index.d.ts +63 -17
- package/dist/server/mcp/index.js +256 -54
- package/dist/server/mcp/index.js.map +1 -1
- package/dist/server/otel.d.ts +3 -3
- package/dist/{sharing-DVa8W-yR.d.ts → sharing-UDLn1049.d.ts} +3 -3
- package/dist/testing/client.d.ts +14 -14
- package/dist/testing/client.js +9 -7
- package/dist/testing/client.js.map +1 -1
- package/dist/testing/index.d.ts +12 -12
- package/dist/testing/index.js +21 -12
- package/dist/testing/index.js.map +1 -1
- package/dist/testing/mock.d.ts +5 -5
- package/dist/testing/mock.js +8 -6
- package/dist/{types-Cu0vmx7c.d.ts → types-CYliTP65.d.ts} +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/package.json +1 -1
- package/dist/chunk-2PWMUVC7.js.map +0 -1
- package/dist/chunk-4DIQHASK.js.map +0 -1
- package/dist/chunk-CDKG4TFA.js.map +0 -1
- package/dist/chunk-DD4SKCNO.js.map +0 -1
- package/dist/chunk-H23KEFBA.js.map +0 -1
- package/dist/chunk-OECZPRPW.js.map +0 -1
- package/dist/chunk-REX5YPHX.js.map +0 -1
- package/dist/chunk-RT6OILOI.js.map +0 -1
- package/dist/chunk-RZTP4IEW.js.map +0 -1
- package/dist/chunk-TBMKJ26R.js.map +0 -1
- package/dist/chunk-VSQBHPWH.js.map +0 -1
- package/dist/chunk-YSWH776Y.js +0 -13
- package/dist/chunk-YSWH776Y.js.map +0 -1
- package/dist/chunk-ZIVPHYRG.js.map +0 -1
- package/dist/logger-BY28acW-.d.ts +0 -16
- /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-
|
|
2
|
-
import { d as ListSort, c as ListQuery, b as ListPage, D as AdminWritable, u as AdminListInput, v as AdminListQuery, y as AdminPage, r as AdminCreateInput, s as AdminCreateQuery, B as AdminUpdateInput, C as AdminUpdateQuery, w as AdminMetaInput, n as AdminServiceMeta, z as AdminSubscribers } from './adminSchemas-
|
|
1
|
+
import { e as StandardSchemaWithJSON, d as StandardSchemaV1, I as InferInput, c as InferOutput, X as RowSchema, Q as QueryDef, P as ProjectionList, p as MutationDef, J as AnyContract, ap as MethodName } from './infer-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-
|
|
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
|
|
115
|
-
*
|
|
116
|
-
*
|
|
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-
|
|
4
|
-
import { P as Principal, M as MaybePromise } from './types-
|
|
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
|
-
*
|
|
17
|
-
* a browser sends with a request any
|
|
18
|
-
* the request's `Origin`
|
|
19
|
-
* `Authorization: Bearer` header, which a
|
|
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
|
|
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
|
-
*
|
|
46
|
-
* cookie, which a browser sends with a
|
|
47
|
-
* `authenticate` that accepts it checks the
|
|
48
|
-
* `socketAuth` does; `"bearer"
|
|
49
|
-
* a page sends only on purpose.
|
|
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).
|
|
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
|
|
105
|
-
*
|
|
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-
|
|
2
|
-
import { L as Logger } from './logger-
|
|
3
|
-
import { A as AccessLevel, J as AnyContract, y as CollectionWhere, O as OrderBy, k as MethodKind, d as StandardSchemaV1, ag as EventName, ah as EventPayloadOf, aa as ChannelName, ab as ChannelPayloadOf, az as StreamName, am as IsScopedStream, ay as StreamItemOf, ae as ContractMap, ap as MethodName, al as InputOf, ar as OutputOf } from './infer-
|
|
4
|
-
import { P as Principal, Q as QuickdrawTypes, M as MaybePromise, c as PrincipalOf, a as McpContext, T as Transport, C as ContextExtensionOf, b as McpContextOf, D as DbOf } from './types-
|
|
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
|
|
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
|
|
101
|
-
*
|
|
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
|
|
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
|
-
/**
|
|
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
|
|
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.
|
|
643
|
-
*
|
|
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
|
|
689
|
-
*
|
|
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
|
-
/**
|
|
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
|
-
/**
|
|
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
|
|
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
|
|
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,
|
|
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-
|
|
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":[]}
|