@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
|
@@ -18,7 +18,13 @@ declare function stripIp(raw: string | undefined): string;
|
|
|
18
18
|
/**
|
|
19
19
|
* Key by Bearer token when present, falling back to client IP. Lets
|
|
20
20
|
* token-authenticated clients get their own bucket instead of sharing
|
|
21
|
-
* a NAT'd IP.
|
|
21
|
+
* a NAT'd IP. Nothing here checks the token (the 5.1 audit's
|
|
22
|
+
* TRANSPORT-4): a caller that sends a new made-up token on each request
|
|
23
|
+
* gets a fresh bucket each time, even behind `requireSession`, which signs
|
|
24
|
+
* a request in by its cookie first and never reads the bearer token then.
|
|
25
|
+
* Key a limiter mounted behind `requireSession` by the user it verified
|
|
26
|
+
* instead (`keyGenerator: (req) => sessionOf(req).userId`), and anonymous
|
|
27
|
+
* traffic by IP, as `createPublicApiLimiter` does.
|
|
22
28
|
*/
|
|
23
29
|
declare function bearerOrIp(req: Request): string;
|
|
24
30
|
/**
|
|
@@ -39,7 +45,8 @@ declare function createPublicApiLimiter(overrides?: PartialOptions): RateLimitRe
|
|
|
39
45
|
* minute per IP, refused with `429 { ok: false, e: { code: "RATE_LIMITED",
|
|
40
46
|
* message, data: { retryAfterMs } } }`. Server-side prefetch reaches the API
|
|
41
47
|
* from the web server's address, so key it some other way (`keyGenerator`)
|
|
42
|
-
* or raise `max` when the web server prefetches for many users
|
|
48
|
+
* or raise `max` when the web server prefetches for many users; never by
|
|
49
|
+
* an unverified bearer token (`bearerOrIp`).
|
|
43
50
|
*/
|
|
44
51
|
declare function createCallLimiter(overrides?: PartialOptions): RateLimitRequestHandler;
|
|
45
52
|
|
package/dist/server/index.d.ts
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
|
-
import { J as AnyContract, at as ProjectionName, ai as FullProjectionOf, ac as CollectionName, ap as MethodName, d as StandardSchemaV1, c as InferOutput, N as NullableProjection, P as ProjectionList, aq as MethodOf, as as ParsedInputOf, ao as KindOf, A as AccessLevel, af as EntityOf, ar as OutputOf } from '../infer-
|
|
2
|
-
import { M as ModelName, v as PolicyFor, w as RowForms, x as AccessFor, H as HandlerContext, y as ModelColumn, z as WatchAccess, B as PublicAccess, E as HandlerArgs, G as CustomAccess, I as ShareMode, J as ChannelOptions, K as StreamOptions, u as RoomLeaveHandler, L as ChannelsRequired, h as Service, N as BaseContext, O as CallerFor, l as RunContext, m as RunOptions, o as StreamHandle, P as Presence, n as ServerRooms, Q as AccessPolicy, T as AnyAccessPolicy, V as PolicyColumns, X as PolicyForeign, Y as PolicyTools, k as AccessFilter, Z as RowAccess, q as AccessEngine, b as StorageWhere } from '../caller-
|
|
3
|
-
export { aI as ANY_FIELD, aA as AccessForm, aB as AccessRequest, _ as AffectsLink, p as AnyContext, A as AnyService, r as AppRooms, aC as AuthenticatedAccess, aq as CallOptions, d as Caller, a9 as ChannelAccess, aa as ChannelContext, ab as ChannelHandler, ac as ChannelImplementation, ad as ChannelRoomOf, a4 as CollectionScope, a6 as ContextRooms, a7 as ContextServices, C as CountArgs, a3 as DEFAULT_BULK_THRESHOLD, i as DevWarning, D as DevWarningError, j as DevWarningKind, aD as EntryAccess, F as FindManyArgs, f as FlushInfo, e as FlushSink, at as ForeignColumns, au as ForeignColumnsOf, aE as IdKeyOf, aF as IdSelector, av as MembershipRead, ar as MethodCaller,
|
|
4
|
-
import { S as ServerOptions, Q as QuickdrawServer } from '../createServer-
|
|
5
|
-
export { C as ClusterClient, u as ClusterOptions, D as DEFAULT_STALL_INTERVAL_MS, e as DEFAULT_STALL_MAX_MS, f as DEFAULT_STALL_THRESHOLD_MS, p as DisconnectUserOptions, E as EntitySubscription, a as EntitySubscriptions, H as HttpApp, I as IoRedisLike, L as LegacyReply, t as LegacyWireOptions, M as MIN_STALL_INTERVAL_MS, m as QuickdrawIo, n as QuickdrawServerSocket, o as QuickdrawSocketData, y as RateLimitOptions, z as RateLimiter, R as RotateOptions, g as STALL_RESOLUTION_MS, b as ScopeSubscription, c as ScopeSubscriptions, l as ServerOnlyOptions, h as SlowMethod, q as SocketCors, r as SocketOptions, s as SocketRateLimitOptions, i as StallReport, j as StallWatchdogOptions, T as TopicWatch, d as TopicWatches, w as applyRateLimitMiddleware, v as createRateLimiter, k as createServer, x as createTieredRateLimiter } from '../createServer-
|
|
6
|
-
import { ad as Version } from '../envelope-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
|
|
15
|
-
export {
|
|
1
|
+
import { J as AnyContract, at as ProjectionName, ai as FullProjectionOf, ac as CollectionName, ap as MethodName, d as StandardSchemaV1, c as InferOutput, N as NullableProjection, P as ProjectionList, aq as MethodOf, as as ParsedInputOf, ao as KindOf, A as AccessLevel, af as EntityOf, ar as OutputOf } from '../infer-BmoZsL9a.js';
|
|
2
|
+
import { M as ModelName, v as PolicyFor, w as RowForms, x as AccessFor, H as HandlerContext, y as ModelColumn, z as WatchAccess, B as PublicAccess, E as HandlerArgs, G as CustomAccess, I as ShareMode, J as ChannelOptions, K as StreamOptions, u as RoomLeaveHandler, L as ChannelsRequired, h as Service, N as BaseContext, O as CallerFor, l as RunContext, m as RunOptions, o as StreamHandle, P as Presence, n as ServerRooms, Q as AccessPolicy, T as AnyAccessPolicy, V as PolicyColumns, X as PolicyForeign, Y as PolicyTools, k as AccessFilter, Z as RowAccess, q as AccessEngine, b as StorageWhere } from '../caller-BT-Mv4bD.js';
|
|
3
|
+
export { aI as ANY_FIELD, aA as AccessForm, aB as AccessRequest, _ as AffectsLink, p as AnyContext, A as AnyService, r as AppRooms, aC as AuthenticatedAccess, aq as CallOptions, d as Caller, a9 as ChannelAccess, aa as ChannelContext, ab as ChannelHandler, ac as ChannelImplementation, ad as ChannelRoomOf, a4 as CollectionScope, a6 as ContextRooms, a7 as ContextServices, C as CountArgs, a3 as DEFAULT_BULK_THRESHOLD, i as DevWarning, D as DevWarningError, j as DevWarningKind, aD as EntryAccess, F as FindManyArgs, f as FlushInfo, e as FlushSink, at as ForeignColumns, au as ForeignColumnsOf, aE as IdKeyOf, aF as IdSelector, aL as LinkInterest, av as MembershipRead, ar as MethodCaller, aN as N_PLUS_ONE_STATEMENTS, aw as ParentLink, ax as PolicyKind, ay as PolicyReads, $ as ProjectedOutput, a0 as Projection, ae as RoomLeave, af as RoomLeaveReason, ag as RoomLeft, ah as RoomTarget, az as RowLevel, R as RowLevels, aG as ScopeAccess, aH as ServiceAccess, as as ServiceCaller, ai as ServiceChannel, a5 as ServiceCollection, g as ServiceMethod, aj as ServiceStream, c as StatementCount, S as StorageAdapter, a as StorageRow, ak as StreamImplementation, al as StreamPushArgs, am as StreamPushManyArgs, an as StreamSeed, ao as StreamSeedContext, t as StreamSubscriptions, ap as StreamValidation, a1 as TierGroup, a2 as Tiers, a8 as TouchOptions, aJ as UnitOfWork, U as UnitOfWorkFactory, aK as UnitOfWorkScope, W as WriteRecord, aM as formatDevWarning, s as storageOf } from '../caller-BT-Mv4bD.js';
|
|
4
|
+
import { S as ServerOptions, Q as QuickdrawServer } from '../createServer-Cp-Wk500.js';
|
|
5
|
+
export { C as ClusterClient, u as ClusterOptions, D as DEFAULT_STALL_INTERVAL_MS, e as DEFAULT_STALL_MAX_MS, f as DEFAULT_STALL_THRESHOLD_MS, p as DisconnectUserOptions, E as EntitySubscription, a as EntitySubscriptions, H as HttpApp, I as IoRedisLike, L as LegacyReply, t as LegacyWireOptions, M as MIN_STALL_INTERVAL_MS, m as QuickdrawIo, n as QuickdrawServerSocket, o as QuickdrawSocketData, y as RateLimitOptions, z as RateLimiter, R as RotateOptions, g as STALL_RESOLUTION_MS, b as ScopeSubscription, c as ScopeSubscriptions, l as ServerOnlyOptions, h as SlowMethod, q as SocketCors, r as SocketOptions, s as SocketRateLimitOptions, i as StallReport, j as StallWatchdogOptions, T as TopicWatch, d as TopicWatches, w as applyRateLimitMiddleware, v as createRateLimiter, k as createServer, x as createTieredRateLimiter } from '../createServer-Cp-Wk500.js';
|
|
6
|
+
import { ad as Version } from '../envelope-DzWyIXjj.js';
|
|
7
|
+
export { ag as MAX_ENTITY_IDS_PER_SOCKET, ah as MAX_SCOPES_PER_SOCKET, ai as MAX_TOPICS_PER_SOCKET } from '../envelope-DzWyIXjj.js';
|
|
8
|
+
import { Q as QuickdrawTypes, D as DbOf, d as PrincipalKindOf, c as PrincipalOf, M as MaybePromise, b as McpContextOf, C as ContextExtensionOf, P as Principal } from '../types-CYliTP65.js';
|
|
9
|
+
export { a as McpContext, T as Transport } from '../types-CYliTP65.js';
|
|
10
|
+
import { c as DispatcherOptions, D as Dispatcher, d as DispatcherCollections } from '../http-DotQ5bj8.js';
|
|
11
|
+
export { p as AccessChange, q as AccessChangeListener, A as AccessOptions, e as ChangeLogOptions, C as ContractOfServices, k as DEFAULT_LIMITS, g as DbOfServices, i as DispatchRequest, j as DispatchResult, o as DispatcherAccess, a as DispatcherBaseOptions, l as DispatcherLimits, u as HttpMiddleware, H as HttpRouter, v as HttpRouterOptions, b as HttpTransportOptions, L as LimitsOptions, m as PipelineOptions, P as PrincipalOfServices, R as RegisteredMethod, h as Registry, S as SharedData, n as SubscriptionLimits, T as TrackingOptions, V as VersionRequest, r as VersionSource, f as createDispatcher, s as createHttpRouter, t as toCallReply } from '../http-DotQ5bj8.js';
|
|
12
|
+
import { l as CrudMethodName, d as CrudContractOptions, S as ScalarFieldOf, N as NumberFieldOf, m as CrudMethods, H as AdminMethodsOf, w as AdminContractOptions, G as AdminMethods } from '../admin-DWWrT1h6.js';
|
|
13
|
+
import { S as SearchContractOptions, T as TextFieldOf, b as SearchMethods, n as SharingMethodName, f as SharingByNameMethod, g as SharingContractOptions, o as SharingMethods } from '../sharing-UDLn1049.js';
|
|
14
|
+
import { x as AdminNeverWritable, l as AdminFieldConfig } from '../adminSchemas-D4bFKsrb.js';
|
|
15
|
+
export { a as CallOutcome, C as CallRecord } from '../metrics-DW80Exve.js';
|
|
16
|
+
export { b as AuthenticateRequest, A as AuthenticateResult, c as HttpAuthenticateRequest, d as HttpCredentialSource, S as ServerAuth, e as ServiceAccessSource, a as ServiceGrants, f as SocketAuthenticateRequest, r as recordSocketSession } from '../auth-DXiSJZaf.js';
|
|
16
17
|
import { Server } from 'socket.io';
|
|
17
|
-
import { L as Logger } from '../logger-
|
|
18
|
+
import { L as Logger } from '../logger-Yz93KzdV.js';
|
|
18
19
|
import 'node:http';
|
|
19
20
|
import '../errors-S-bxEIBC.js';
|
|
20
21
|
|
|
@@ -31,9 +32,6 @@ declare const MAX_APP_ROOMS = 100;
|
|
|
31
32
|
/** The most scopes one stream keeps a seed for; the scope pushed to least recently goes first. */
|
|
32
33
|
declare const STREAM_MAX_SCOPES = 10000;
|
|
33
34
|
|
|
34
|
-
/** The most stream feeds one socket may subscribe to at once. */
|
|
35
|
-
declare const MAX_STREAMS_PER_SOCKET = 500;
|
|
36
|
-
|
|
37
35
|
/**
|
|
38
36
|
* A JSON column's value as a database client types it: structurally
|
|
39
37
|
* Prisma's `JsonValue` (and its `InputJsonValue` objects and arrays). A row
|
|
@@ -113,6 +111,9 @@ type HandlerOutputOf<C extends AnyContract, M extends MethodName<C>, Proj = Reco
|
|
|
113
111
|
* that row, through its service's policy. Deleting the row closes its
|
|
114
112
|
* scopes (`qd:revoked` with reason `"anchor-deleted"`).
|
|
115
113
|
* - `scopeAccess: "self"`: the scope value is the subscriber's own user id.
|
|
114
|
+
* On a service with an access policy its items still follow that policy:
|
|
115
|
+
* only the rows its user may read at `Read`, and a row the user loses goes
|
|
116
|
+
* out `removed`.
|
|
116
117
|
*
|
|
117
118
|
* A service-wide `Admin` grant on this service passes either check, as it
|
|
118
119
|
* passes every check of the service. `bulkThreshold` (default 200) is how
|
|
@@ -208,9 +209,15 @@ type ShareOptions<A> = {
|
|
|
208
209
|
/**
|
|
209
210
|
* Run identical concurrent calls once: `"caller"` per principal, `"all"`
|
|
210
211
|
* across principals. Every caller is authorized before it joins; the
|
|
211
|
-
* handler runs with the first caller's `ctx`,
|
|
212
|
-
*
|
|
213
|
-
*
|
|
212
|
+
* handler runs with the first caller's `ctx`, and every caller gets its
|
|
213
|
+
* result, so a `"all"` result must depend on the input only: its
|
|
214
|
+
* handler reads nothing of the caller (in development, reading
|
|
215
|
+
* `ctx.principal`, `ctx.services` or `ctx.mcp` warns
|
|
216
|
+
* `[quickdraw:shared-principal]`, and a strict test app fails the
|
|
217
|
+
* call). `"all"` is not allowed with `custom` access, nor on the kit
|
|
218
|
+
* methods whose result depends on who asks (the read/write kit's
|
|
219
|
+
* `list` and `getMany`, `search`, the sharing kit's `listShares` and
|
|
220
|
+
* `listMembers`, the admin kit's `adminList` and `adminGet`).
|
|
214
221
|
*/
|
|
215
222
|
readonly share: A extends CustomAccess<never, never> ? "caller" : ShareMode;
|
|
216
223
|
/** Reuse a successful result for this long after its run, in milliseconds. */
|
|
@@ -252,6 +259,22 @@ type MethodImplementation<T extends QuickdrawTypes, C extends AnyContract, M ext
|
|
|
252
259
|
* signed out. Leave it out for the service's list.
|
|
253
260
|
*/
|
|
254
261
|
readonly kinds?: readonly PrincipalKindOf<T>[];
|
|
262
|
+
/**
|
|
263
|
+
* A requirement on the caller's verified claims (`principal.claims`, a
|
|
264
|
+
* token's `scope`), beside `access`: the call passes only when it returns
|
|
265
|
+
* `true`. It adds to the form, so `{ entry: "Moderate" }` with `claims`
|
|
266
|
+
* needs Moderate on the row and the claims. It runs right after `kinds`,
|
|
267
|
+
* outside the access engine, so no grant passes it, a service-wide
|
|
268
|
+
* `Admin` grant included. Without a principal the call is
|
|
269
|
+
* `UNAUTHENTICATED`; when it returns anything but `true`, `FORBIDDEN`. It
|
|
270
|
+
* is synchronous, and a `"public"` method takes none. Never write a check
|
|
271
|
+
* on claims as `custom`: that form replaces the row check, and an Admin
|
|
272
|
+
* grant skips it.
|
|
273
|
+
*
|
|
274
|
+
* @example
|
|
275
|
+
* claims: (claims) => claims?.scope === "triage",
|
|
276
|
+
*/
|
|
277
|
+
readonly claims?: (claims: PrincipalOf<T>["claims"], input: ParsedInputOf<C, M>) => boolean;
|
|
255
278
|
/**
|
|
256
279
|
* Runs the method. For a projection output it returns the database row
|
|
257
280
|
* (or rows, or `null`), which the framework projects: only the
|
|
@@ -263,11 +286,15 @@ type MethodImplementation<T extends QuickdrawTypes, C extends AnyContract, M ext
|
|
|
263
286
|
/**
|
|
264
287
|
* Says the access form is this method's whole check on purpose. On a
|
|
265
288
|
* service with an access policy, `defineService` refuses a method whose
|
|
266
|
-
* input has `id` while its access
|
|
267
|
-
* service: L }` below `Admin`,
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
289
|
+
* input has `id` while its access checks some other row or none:
|
|
290
|
+
* `"public"`, `"authenticated"`, `{ service: L }` below `Admin`,
|
|
291
|
+
* `custom(fn)`, `{ scope, of, id }`, or `{ entry, id }` naming another
|
|
292
|
+
* key. Any caller the form admits could then reach any row by its id:
|
|
293
|
+
* give it `{ entry: L }` so the policy decides (with `claims` beside it
|
|
294
|
+
* for a check on the caller's claims), or `rowless: true` when every such
|
|
295
|
+
* caller may reach any row (public profiles, lookups by an id that tells
|
|
296
|
+
* nothing), or when `id` names a row the method creates (an id the client
|
|
297
|
+
* made).
|
|
271
298
|
*/
|
|
272
299
|
readonly rowless?: true;
|
|
273
300
|
} & (KindOf<C, M> extends "query" ? QueryOptions<T, C, M, A> : MutationOptions);
|
|
@@ -315,7 +342,8 @@ interface ServiceDefinition<T extends QuickdrawTypes, C extends AnyContract, A,
|
|
|
315
342
|
* section 7.1): `{ anchor: contract }`, the contract whose rows the scope
|
|
316
343
|
* values are ids of, or `{ scopeAccess: "self" }` for a scope that is the
|
|
317
344
|
* subscriber's user id; plus `bulkThreshold`. Required when the contract
|
|
318
|
-
* declares collections. Needs `model`.
|
|
345
|
+
* declares collections. Needs `model`. A self scope's items still follow
|
|
346
|
+
* the service's `access` policy, when it has one.
|
|
319
347
|
*/
|
|
320
348
|
readonly collections?: CollectionOptions<C>;
|
|
321
349
|
/**
|
|
@@ -535,6 +563,9 @@ declare const CHANNEL_ABUSE_WINDOW_MS = 10000;
|
|
|
535
563
|
/** A socket is disconnected once its drops within the window exceed this many times the channel's rate. */
|
|
536
564
|
declare const CHANNEL_ABUSE_MULTIPLIER = 100;
|
|
537
565
|
|
|
566
|
+
/** The most stream feeds one socket may subscribe to at once. */
|
|
567
|
+
declare const MAX_STREAMS_PER_SOCKET = 500;
|
|
568
|
+
|
|
538
569
|
/**
|
|
539
570
|
* A custom access check: the call passes when `check(ctx, input)` resolves
|
|
540
571
|
* `true`. The caller must be authenticated first, so `ctx.principal` is
|
|
@@ -543,6 +574,11 @@ declare const CHANNEL_ABUSE_MULTIPLIER = 100;
|
|
|
543
574
|
* custom access cannot `share: "all"`, because its result may depend on who
|
|
544
575
|
* asks.
|
|
545
576
|
*
|
|
577
|
+
* It replaces the row check: on a service with an access policy,
|
|
578
|
+
* `defineService` refuses it on a method whose input has `id` unless the
|
|
579
|
+
* method says `rowless: true`. A check on the caller's claims is the
|
|
580
|
+
* method's `claims`, beside an `entry` form: no grant skips that.
|
|
581
|
+
*
|
|
546
582
|
* @example
|
|
547
583
|
* access: custom((ctx, input) => ctx.principal.userId === input.ownerId),
|
|
548
584
|
*/
|
|
@@ -578,7 +614,11 @@ declare function jsonAcl<const Field extends string, const Owner extends string
|
|
|
578
614
|
|
|
579
615
|
/** Options of {@link members}. */
|
|
580
616
|
interface MembersOptions<Model extends string = string, Entry extends string = string, User extends string = string, Level extends string = string> {
|
|
581
|
-
/**
|
|
617
|
+
/**
|
|
618
|
+
* The membership model, named as the client names it: `"projectMember"`.
|
|
619
|
+
* It needs an `id` column (keep the pair unique with `@@unique`, never a
|
|
620
|
+
* composite `@@id`), or its writes are not tracked and never revoke.
|
|
621
|
+
*/
|
|
582
622
|
readonly model: Model;
|
|
583
623
|
/** The column holding the id of the row the membership is on: `"projectId"`. */
|
|
584
624
|
readonly entry: Entry;
|
|
@@ -589,14 +629,20 @@ interface MembersOptions<Model extends string = string, Entry extends string = s
|
|
|
589
629
|
/**
|
|
590
630
|
* Maps stored roles to access levels: `{ owner: "Admin", editor: "Moderate", viewer: "Read" }`.
|
|
591
631
|
* Without it a stored role must be a level name. A role that maps to no
|
|
592
|
-
* level grants nothing.
|
|
632
|
+
* level grants nothing. Roles that map to one level are peers: with
|
|
633
|
+
* `{ owner: "Admin", admin: "Admin" }` the sharing kit lets an `admin`
|
|
634
|
+
* demote or remove the `owner`. To make an owner untouchable, give the
|
|
635
|
+
* row an owner column (`anyOf(owner("ownerId"), members(...))`, or
|
|
636
|
+
* `jsonAcl(..., { owner })`).
|
|
593
637
|
*/
|
|
594
638
|
readonly levels?: Readonly<Record<string, AccessLevel>>;
|
|
595
639
|
}
|
|
596
640
|
/**
|
|
597
641
|
* The level comes from the principal's row in a membership table: one query
|
|
598
642
|
* per lookup, filtered by the entry ids and the user id. Several rows for one
|
|
599
|
-
* user and entry give the highest level among them.
|
|
643
|
+
* user and entry give the highest level among them. The table needs an `id`
|
|
644
|
+
* column, or no write to it is tracked and a removed member keeps what they
|
|
645
|
+
* subscribed to; `createServer` names such a table at start.
|
|
600
646
|
*
|
|
601
647
|
* @example
|
|
602
648
|
* access: members({ model: "projectMember", entry: "projectId", user: "userId", level: "role" }),
|
|
@@ -666,7 +712,8 @@ interface ResolverOptions<P extends Principal = Principal, Column extends string
|
|
|
666
712
|
/**
|
|
667
713
|
* A filter matching exactly the rows on which the principal has at least
|
|
668
714
|
* `level`, or `"none"`. Without it, list filters through this policy match
|
|
669
|
-
* no row
|
|
715
|
+
* no row, and the read/write kit's `reorder` moves none below a
|
|
716
|
+
* service-wide grant.
|
|
670
717
|
*/
|
|
671
718
|
readonly where?: (principal: P, level: AccessLevel, tools: PolicyTools) => MaybePromise<AccessFilter>;
|
|
672
719
|
/**
|
|
@@ -819,22 +866,31 @@ interface CrudHandlersOptions<C extends AnyContract, A, Db = unknown> {
|
|
|
819
866
|
* Who may call each of the kit's methods: one form per method, required.
|
|
820
867
|
* `get`, `update`, `delete` and `reorder` take `{ entry: L }` to check the
|
|
821
868
|
* row; the methods on many rows also keep only the rows the service's
|
|
822
|
-
* policy gives the caller at the form's level.
|
|
869
|
+
* policy gives the caller at the form's level. A bulk method whose form
|
|
870
|
+
* names no level takes its single-row counterpart's row level (`delete`'s
|
|
871
|
+
* for `bulkDelete`, `update`'s for `bulkUpdate`) when it is higher than
|
|
872
|
+
* `Moderate`; give `{ entry: L, id: "ids" }` to refuse the whole call
|
|
873
|
+
* instead of skipping rows.
|
|
823
874
|
*/
|
|
824
875
|
readonly access: A & NoInfer<NotAKitMethod<C, A>>;
|
|
825
876
|
/**
|
|
826
877
|
* What `create` writes, from its parsed input: set owner or scope columns
|
|
827
878
|
* from `ctx.principal`, an ordinal from `nextOrdinal`. `create` writes the
|
|
828
|
-
* input as it is without it.
|
|
879
|
+
* input as it is without it. An owner or access-list column (one the
|
|
880
|
+
* policy reads) comes only from here: the caller may not send one. The
|
|
881
|
+
* anchors of what it returns (each `inherit` parent and anchored
|
|
882
|
+
* collection's scope it sets) are checked at the method's row level, and
|
|
883
|
+
* it returns column values only, never a relation or a nested write.
|
|
829
884
|
*/
|
|
830
885
|
readonly prepare?: [CrudCreateOf<C>] extends [never] ? `crud.handlers: prepare is for the kit's create, which ${C["name"]} does not have` : CrudPrepare<ParsedInputOf<C, CrudCreateOf<C>>, KitContext, Db>;
|
|
831
886
|
/**
|
|
832
887
|
* The kit's methods whose access form is their whole check on purpose:
|
|
833
888
|
* each gets `rowless: true`. On a service with an access policy,
|
|
834
|
-
* `defineService` refuses `get` under
|
|
835
|
-
*
|
|
836
|
-
*
|
|
837
|
-
*
|
|
889
|
+
* `defineService` refuses `get` under a form that asks the policy nothing
|
|
890
|
+
* about the row its `id` names (`"public"`, `"authenticated"`,
|
|
891
|
+
* `{ service: L }` below `Admin`, `custom`, `{ scope }`, or `{ entry }` on
|
|
892
|
+
* another key: any such caller could read any row by its id) unless it is
|
|
893
|
+
* named here: `rowless: ["get"]` for public profiles, say. `update`, `delete` and `reorder` check the row whatever
|
|
838
894
|
* their form, and need nothing.
|
|
839
895
|
*/
|
|
840
896
|
readonly rowless?: readonly CrudMethodsOf<C>[];
|
|
@@ -963,7 +1019,8 @@ type SearchStrategy<Ctx = SearchStrategyContext> = {
|
|
|
963
1019
|
* The filter of the rows that match `q`, in the database client's
|
|
964
1020
|
* `where` shape: a Postgres full-text condition, say. The kit adds the
|
|
965
1021
|
* access filter and the scope, and pages the rows by keyset cursor in
|
|
966
|
-
* the scope collection's order
|
|
1022
|
+
* the scope collection's order when the call passes a scope, else by
|
|
1023
|
+
* id.
|
|
967
1024
|
*/
|
|
968
1025
|
readonly where: (q: string, ctx: Ctx) => MaybePromise<StorageWhere>;
|
|
969
1026
|
readonly ids?: never;
|
|
@@ -1086,9 +1143,13 @@ interface SharingUserLookup {
|
|
|
1086
1143
|
readonly email?: string;
|
|
1087
1144
|
}
|
|
1088
1145
|
/**
|
|
1089
|
-
* Finds the user a by-name method means: their user id, or `null` (or
|
|
1146
|
+
* Finds the one user a by-name method means: their user id, or `null` (or
|
|
1090
1147
|
* `undefined`) when there is no such user, which fails the call with
|
|
1091
|
-
* `NOT_FOUND`.
|
|
1148
|
+
* `NOT_FOUND`. It must name exactly one user: look a user up by something
|
|
1149
|
+
* unique and verified (an email, with `findUnique`), and take a display
|
|
1150
|
+
* name's user only when exactly one user has that name, else `null`. A
|
|
1151
|
+
* `findFirst` by name hands the share to whoever registered the name first
|
|
1152
|
+
* (the 5.1 audit's SHARE-2).
|
|
1092
1153
|
*/
|
|
1093
1154
|
type SharingResolveUser<Db = unknown> = (lookup: SharingUserLookup, ctx: KitContext, db: Db) => MaybePromise<string | null | undefined>;
|
|
1094
1155
|
type NotASharingMethod<C extends AnyContract, A> = [Exclude<keyof A, SharingMethodsOf<C>>] extends [
|
|
@@ -1110,17 +1171,25 @@ type SharingHandlersOptions<C extends AnyContract, A, Db = unknown> = {
|
|
|
1110
1171
|
readonly onChange?: SharingOnChange<Db>;
|
|
1111
1172
|
/**
|
|
1112
1173
|
* The kit's methods whose access form is their whole check on purpose:
|
|
1113
|
-
* each gets `rowless: true`.
|
|
1174
|
+
* each gets `rowless: true`. A change still never touches a user above
|
|
1175
|
+
* the caller's own level on the row, which under such a form is their
|
|
1176
|
+
* level on the row (often none): name `service` in the form for a
|
|
1177
|
+
* service-wide grant to count. On a service with an access policy,
|
|
1114
1178
|
* `defineService` refuses an access-list method (`share`, `unshare`,
|
|
1115
1179
|
* `setLevel`, `listShares`, `shareByName`: their input names the row as
|
|
1116
|
-
* `id`) under
|
|
1117
|
-
* `
|
|
1180
|
+
* `id`) under a form that asks the policy nothing about that row
|
|
1181
|
+
* (`"public"`, `"authenticated"`, `{ service: L }` below `Admin`,
|
|
1182
|
+
* `custom`, `{ scope }`, or `{ entry }` on another key) unless it is named
|
|
1183
|
+
* here.
|
|
1118
1184
|
*/
|
|
1119
1185
|
readonly rowless?: readonly SharingMethodsOf<C>[];
|
|
1120
1186
|
} & ([SharingByNameOf<C>] extends [never] ? {
|
|
1121
1187
|
readonly resolveUser?: `sharing.handlers: resolveUser is for shareByName and inviteByName, which ${C["name"]} does not have`;
|
|
1122
1188
|
} : {
|
|
1123
|
-
/**
|
|
1189
|
+
/**
|
|
1190
|
+
* Finds the one user `shareByName` and `inviteByName` mean, by name or
|
|
1191
|
+
* email, or `null`: never the first of several (`SharingResolveUser`).
|
|
1192
|
+
*/
|
|
1124
1193
|
readonly resolveUser: SharingResolveUser<Db>;
|
|
1125
1194
|
});
|
|
1126
1195
|
/** `sharing.handlers`' options argument: required when the contract has a by-name method. */
|
|
@@ -1233,26 +1302,29 @@ interface AdminHandlersOptions<C extends AnyContract, A, Db = unknown> {
|
|
|
1233
1302
|
readonly displayName?: string;
|
|
1234
1303
|
/**
|
|
1235
1304
|
* Fields the kit leaves out entirely: from `adminMeta`, from the rows it
|
|
1236
|
-
* returns and from what it writes. `acl`, `serviceAccess
|
|
1237
|
-
* `service_access`
|
|
1238
|
-
* the
|
|
1305
|
+
* returns and from what it writes. `acl`, `serviceAccess`,
|
|
1306
|
+
* `service_access` and the column the dispatcher's `serviceAccessSource`
|
|
1307
|
+
* names on this service's model are always hidden (the first three as in
|
|
1308
|
+
* 4.1), unless `grants` shows the grants field.
|
|
1239
1309
|
*/
|
|
1240
1310
|
readonly hiddenFields?: readonly Exclude<AdminFieldOf<C>, "id">[];
|
|
1241
1311
|
/**
|
|
1242
|
-
* Show and write the entity's
|
|
1243
|
-
*
|
|
1244
|
-
*
|
|
1245
|
-
*
|
|
1246
|
-
*
|
|
1247
|
-
*
|
|
1248
|
-
*
|
|
1249
|
-
*
|
|
1250
|
-
*
|
|
1251
|
-
*
|
|
1252
|
-
*
|
|
1253
|
-
*
|
|
1254
|
-
|
|
1255
|
-
|
|
1312
|
+
* Show and write the entity's grants field, the user's service-wide
|
|
1313
|
+
* grants, which the kit hides by default: `serviceAccess`,
|
|
1314
|
+
* `service_access`, or the column `auth.serviceAccessSource` names on this
|
|
1315
|
+
* service's model, whatever its name. An admin screen then edits grants
|
|
1316
|
+
* through `adminUpdate` (a JSON field, checked by the entity's schema).
|
|
1317
|
+
* Only a caller whose own service-wide grant on this service is `Admin`
|
|
1318
|
+
* reads or writes it, whatever `access` gives a method; anyone else is
|
|
1319
|
+
* refused a write, filter or sort naming it (`FORBIDDEN`) and gets rows
|
|
1320
|
+
* without it. Such an Admin can grant any service, themself included, so
|
|
1321
|
+
* give that grant only to those who may. With `auth.serviceAccessSource`
|
|
1322
|
+
* naming the column, a change reaches the user's open sockets on every
|
|
1323
|
+
* node at once (`qd:access`, their subscriptions resolved again), as any
|
|
1324
|
+
* tracked write to it does. The dispatcher refuses `true` for an entity
|
|
1325
|
+
* without a grants field, when it is made. Default `false`.
|
|
1326
|
+
*/
|
|
1327
|
+
readonly grants?: boolean;
|
|
1256
1328
|
/**
|
|
1257
1329
|
* The only fields `adminCreate` and `adminUpdate` write (finding R1.4 of
|
|
1258
1330
|
* the 5.0.0 review): `adminMeta` reports every other field `editable:
|
|
@@ -1273,9 +1345,10 @@ interface AdminHandlersOptions<C extends AnyContract, A, Db = unknown> {
|
|
|
1273
1345
|
* The kit's methods whose access form is their whole check on purpose:
|
|
1274
1346
|
* each gets `rowless: true`. On a service with an access policy,
|
|
1275
1347
|
* `defineService` refuses `adminGet`, `adminUpdate` or `adminDelete`
|
|
1276
|
-
* under a form below the default that
|
|
1277
|
-
* "Moderate" }`, `"authenticated"
|
|
1278
|
-
*
|
|
1348
|
+
* under a form below the default that asks the policy nothing about the
|
|
1349
|
+
* row its `id` names (`{ service: "Moderate" }`, `"authenticated"`,
|
|
1350
|
+
* `custom`, `{ scope }`, or `{ entry }` on another key) unless it is named
|
|
1351
|
+
* here, since any caller the form admits reaches any row by its id.
|
|
1279
1352
|
*/
|
|
1280
1353
|
readonly rowless?: readonly AdminMethodsOf<C>[];
|
|
1281
1354
|
/**
|
|
@@ -1411,11 +1484,18 @@ interface RedisAdapterOptions {
|
|
|
1411
1484
|
*/
|
|
1412
1485
|
password?: string;
|
|
1413
1486
|
/**
|
|
1414
|
-
* Redis database number (default: 0)
|
|
1487
|
+
* Redis database number (default: 0). It does not separate deployments:
|
|
1488
|
+
* pub/sub is shared across a server's databases, so two deployments on one
|
|
1489
|
+
* Valkey hear each other's events whatever their `db`; give each its own
|
|
1490
|
+
* Valkey, or its own `cluster.keyPrefix` (docs/deploying.md).
|
|
1415
1491
|
*/
|
|
1416
1492
|
db?: number;
|
|
1417
1493
|
/**
|
|
1418
|
-
*
|
|
1494
|
+
* The adapter's key, which names its pub/sub channels. Default: the
|
|
1495
|
+
* server's `cluster.keyPrefix` ("quickdraw" without one), so deployments
|
|
1496
|
+
* with their own prefixes use their own channels. 5.1.0 and earlier used
|
|
1497
|
+
* "socket.io": pass `keyPrefix: "socket.io"` for the deploy that moves a
|
|
1498
|
+
* cluster from such nodes, so old and new nodes keep hearing each other.
|
|
1419
1499
|
*/
|
|
1420
1500
|
keyPrefix?: string;
|
|
1421
1501
|
/**
|