@procedures/client 0.14.1 → 0.15.1
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/CHANGELOG.md +93 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/adapters/http/client/transport.d.ts.map +1 -1
- package/dist/adapters/http/client/transport.js +4 -0
- package/dist/adapters/http/client/transport.js.map +1 -1
- package/dist/adapters/http/server/handler.d.ts.map +1 -1
- package/dist/adapters/http/server/handler.js +8 -3
- package/dist/adapters/http/server/handler.js.map +1 -1
- package/dist/adapters/local/client/transport.d.ts.map +1 -1
- package/dist/adapters/local/client/transport.js +5 -0
- package/dist/adapters/local/client/transport.js.map +1 -1
- package/dist/browser.d.ts +4 -3
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +2 -0
- package/dist/browser.js.map +1 -1
- package/dist/client/client.js +1 -1
- package/dist/client/client.js.map +1 -1
- package/dist/client/errors/registry.d.ts.map +1 -1
- package/dist/client/errors/registry.js +13 -0
- package/dist/client/errors/registry.js.map +1 -1
- package/dist/client/middleware/rate-limit.d.ts +17 -0
- package/dist/client/middleware/rate-limit.d.ts.map +1 -1
- package/dist/client/middleware/rate-limit.js +94 -39
- package/dist/client/middleware/rate-limit.js.map +1 -1
- package/dist/client/types.d.ts +6 -1
- package/dist/client/types.d.ts.map +1 -1
- package/dist/client/types.js +7 -1
- package/dist/client/types.js.map +1 -1
- package/dist/events/index.d.ts +1 -0
- package/dist/events/index.d.ts.map +1 -1
- package/dist/events/index.js.map +1 -1
- package/dist/events/publisher.d.ts +46 -1
- package/dist/events/publisher.d.ts.map +1 -1
- package/dist/events/publisher.js +95 -5
- package/dist/events/publisher.js.map +1 -1
- package/dist/events/recheck.d.ts +84 -0
- package/dist/events/recheck.d.ts.map +1 -0
- package/dist/events/recheck.js +135 -0
- package/dist/events/recheck.js.map +1 -0
- package/dist/procedures/collection/index.d.ts +3 -1
- package/dist/procedures/collection/index.d.ts.map +1 -1
- package/dist/procedures/collection/index.js +2 -0
- package/dist/procedures/collection/index.js.map +1 -1
- package/dist/procedures/collection/precondition.d.ts +111 -0
- package/dist/procedures/collection/precondition.d.ts.map +1 -0
- package/dist/procedures/collection/precondition.js +153 -0
- package/dist/procedures/collection/precondition.js.map +1 -0
- package/dist/procedures/collection/procedures.d.ts.map +1 -1
- package/dist/procedures/collection/procedures.js +16 -3
- package/dist/procedures/collection/procedures.js.map +1 -1
- package/dist/procedures/collection/query.d.ts +2 -0
- package/dist/procedures/collection/query.d.ts.map +1 -1
- package/dist/procedures/collection/query.js +3 -0
- package/dist/procedures/collection/query.js.map +1 -1
- package/dist/procedures/collection/schemas.d.ts +16 -0
- package/dist/procedures/collection/schemas.d.ts.map +1 -1
- package/dist/procedures/collection/schemas.js +26 -5
- package/dist/procedures/collection/schemas.js.map +1 -1
- package/dist/procedures/collection/storage.d.ts +34 -6
- package/dist/procedures/collection/storage.d.ts.map +1 -1
- package/dist/procedures/collection/storage.js +3 -0
- package/dist/procedures/collection/storage.js.map +1 -1
- package/dist/procedures/index.d.ts +2 -2
- package/dist/procedures/index.d.ts.map +1 -1
- package/dist/procedures/index.js +1 -1
- package/dist/procedures/index.js.map +1 -1
- package/dist/protocol.d.ts +1 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +2 -0
- package/dist/protocol.js.map +1 -1
- package/dist/server/host-policy.d.ts +17 -0
- package/dist/server/host-policy.d.ts.map +1 -1
- package/dist/server/host-policy.js +7 -0
- package/dist/server/host-policy.js.map +1 -1
- package/dist/server/limits.d.ts +122 -0
- package/dist/server/limits.d.ts.map +1 -0
- package/dist/server/limits.js +153 -0
- package/dist/server/limits.js.map +1 -0
- package/dist/server/procedure-server.d.ts +31 -1
- package/dist/server/procedure-server.d.ts.map +1 -1
- package/dist/server/procedure-server.js +43 -2
- package/dist/server/procedure-server.js.map +1 -1
- package/dist/server/request-auth.d.ts +5 -1
- package/dist/server/request-auth.d.ts.map +1 -1
- package/dist/server/request-auth.js +19 -2
- package/dist/server/request-auth.js.map +1 -1
- package/package.json +3 -3
- package/src/adapters/http/client/transport.ts +3 -0
- package/src/adapters/http/server/handler.ts +8 -3
- package/src/adapters/local/client/transport.ts +4 -0
- package/src/browser.ts +20 -0
- package/src/client/client.ts +1 -1
- package/src/client/errors/registry.ts +14 -0
- package/src/client/middleware/rate-limit.ts +116 -41
- package/src/client/types.ts +8 -0
- package/src/events/index.ts +1 -0
- package/src/events/publisher.ts +116 -6
- package/src/events/recheck.ts +182 -0
- package/src/procedures/collection/index.ts +17 -0
- package/src/procedures/collection/precondition.ts +180 -0
- package/src/procedures/collection/procedures.ts +17 -4
- package/src/procedures/collection/query.ts +16 -0
- package/src/procedures/collection/schemas.ts +43 -6
- package/src/procedures/collection/storage.ts +36 -6
- package/src/procedures/index.ts +15 -0
- package/src/protocol.ts +5 -1
- package/src/server/host-policy.ts +22 -0
- package/src/server/limits.ts +254 -0
- package/src/server/procedure-server.ts +59 -3
- package/src/server/request-auth.ts +19 -2
|
@@ -20,6 +20,7 @@ import { createCollectionProcedures } from "../procedures/collection/procedures.
|
|
|
20
20
|
import { InvocationError, invokeProcedure, type AdmitItem, type Interceptor, type Invocation } from "../procedures/invoke.js";
|
|
21
21
|
import type { Capability, ClearanceOrder, ProcedureContext, ResourcesOf } from "../procedures/types.js";
|
|
22
22
|
import type { CollectionPartitions } from "../procedures/collection/partition.js";
|
|
23
|
+
import { VERSION_CONFLICTS_METADATA_KEY, versionConflictsOf } from "../procedures/collection/precondition.js";
|
|
23
24
|
import { TIMEOUT_METADATA_KEY, effectsOf, joinNarrowing, narrowCapability } from "../procedures/capability.js";
|
|
24
25
|
import { IdempotencyCache, idempotencyKeyOf, isRepeatable, type IdempotencyOptions } from "../procedures/idempotency.js";
|
|
25
26
|
import { optionalMs, type Duration } from "../duration.js";
|
|
@@ -32,6 +33,8 @@ import { getGlobalEventBus } from "../events/bus.js";
|
|
|
32
33
|
import type { CollectionProceduresOptions } from "../procedures/collection/procedures.js";
|
|
33
34
|
import type { NarrowingNote } from "../procedures/narrowing.js";
|
|
34
35
|
import type { ServerDebugReport } from "./types.js";
|
|
36
|
+
import type { KeyedLimiter, LimiterStore } from "@mark1russell7/service/limiter";
|
|
37
|
+
import { callLimiter, limitedCall, rateLimitedResponse, type CallLimit, type CallLimiter } from "./limits.js";
|
|
35
38
|
|
|
36
39
|
// =============================================================================
|
|
37
40
|
// Types
|
|
@@ -168,7 +171,8 @@ export interface ProcedureServerOptions extends ServerOptions {
|
|
|
168
171
|
* The remote events of the server (`documentation/EVENTS.md`): a bus and its topic rule, or an
|
|
169
172
|
* `EventPublisher`. The server pins the procedure `events.subscribe`, outside its registry, so a
|
|
170
173
|
* peer can subscribe to the topics. The procedures of the server get this bus as `ctx.bus`.
|
|
171
|
-
* `stop()` ends each subscription first, with `UNAVAILABLE`.
|
|
174
|
+
* `stop()` ends each subscription first, with `UNAVAILABLE`. A publisher that the server makes
|
|
175
|
+
* from the options gets the clock of the server, unless the options give a clock (CLI-44).
|
|
172
176
|
*/
|
|
173
177
|
events?: EventPublisherOptions | EventPublisher | undefined;
|
|
174
178
|
|
|
@@ -180,6 +184,21 @@ export interface ProcedureServerOptions extends ServerOptions {
|
|
|
180
184
|
* @default false
|
|
181
185
|
*/
|
|
182
186
|
debug?: boolean | ((report: ServerDebugReport) => void) | undefined;
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* The limits of the calls (CLI-40). Each limit is a keyed limiter of
|
|
190
|
+
* `@mark1russell7/service/limiter` on the clock of the server. The server takes one unit of each
|
|
191
|
+
* limit that covers a call, after the authentication and before the run, for each face. A
|
|
192
|
+
* refusal is `RATE_LIMITED` with the wait in `retry-after`: HTTP 429 with `Retry-After`. Without
|
|
193
|
+
* this option, the server limits nothing.
|
|
194
|
+
*
|
|
195
|
+
* The server takes the units in the sequence of the limits, and the first refusal stops the call.
|
|
196
|
+
* A limit before the refusal keeps its unit: the server does not give it back.
|
|
197
|
+
*/
|
|
198
|
+
limits?: readonly CallLimit[] | undefined;
|
|
199
|
+
|
|
200
|
+
/** The stores that the replicas share, by the name of their dependency, for each limit with a shared store. */
|
|
201
|
+
limitStores?: Readonly<Record<string, LimiterStore>> | undefined;
|
|
183
202
|
}
|
|
184
203
|
|
|
185
204
|
/** The options of `registerProcedure()`, `registerProcedures()` and `registerModule()` (CLI-47). */
|
|
@@ -335,6 +354,9 @@ export function errorResponse(id: string, error: unknown): ServerResponse<unknow
|
|
|
335
354
|
const retryAfterMs = (error as { retryAfterMs?: unknown } | undefined)?.retryAfterMs;
|
|
336
355
|
const metadata: Record<string, unknown> = reason ? { reason } : {};
|
|
337
356
|
if (typeof retryAfterMs === "number" && Number.isFinite(retryAfterMs) && retryAfterMs >= 0) metadata["retry-after"] = retryAfterMs;
|
|
357
|
+
// A VERSION_CONFLICT error tells the current item of each conflict (roadmap CLI-16)
|
|
358
|
+
const conflicts = versionConflictsOf(error);
|
|
359
|
+
if (conflicts !== undefined) metadata[VERSION_CONFLICTS_METADATA_KEY] = conflicts;
|
|
338
360
|
const code = errorCodeOf(error);
|
|
339
361
|
return {
|
|
340
362
|
id,
|
|
@@ -435,6 +457,8 @@ export class ProcedureServer extends Server implements RepositoryProvider, StopP
|
|
|
435
457
|
readonly ambientCredentials: AmbientCredentials | undefined;
|
|
436
458
|
/** The receiver of the debug reports, or undefined when the option `debug` is off. */
|
|
437
459
|
private readonly debugReport: ((report: ServerDebugReport) => void) | undefined;
|
|
460
|
+
/** The limits of the calls (CLI-40), or undefined without the option `limits`. */
|
|
461
|
+
private readonly callLimits: CallLimiter | undefined;
|
|
438
462
|
|
|
439
463
|
constructor(options: ProcedureServerOptions = {}) {
|
|
440
464
|
super(options);
|
|
@@ -462,9 +486,13 @@ export class ProcedureServer extends Server implements RepositoryProvider, StopP
|
|
|
462
486
|
: options.debug === true
|
|
463
487
|
? (report) => console.debug(`[ProcedureServer] ${report.topic} of ${report.requestId}: ${report.message}`)
|
|
464
488
|
: undefined;
|
|
489
|
+
// The limits of the calls: one keyed limiter for each limit, on the clock of the server (CLI-40)
|
|
490
|
+
this.callLimits = options.limits && options.limits.length > 0 ? callLimiter(options.limits, { clock: this.clock, stores: options.limitStores }) : undefined;
|
|
465
491
|
// The remote events: the server pins events.subscribe, as a host pins host.health (EVENTS.md).
|
|
466
|
-
// The topic rule is its expose rule, so the pin is always on (CLI-47).
|
|
467
|
-
|
|
492
|
+
// The topic rule is its expose rule, so the pin is always on (CLI-47). The publisher that the
|
|
493
|
+
// server makes gets the clock of the server for its re-checks (CLI-44).
|
|
494
|
+
const events = options.events;
|
|
495
|
+
this.publisher = events instanceof EventPublisher ? events : events ? new EventPublisher({ ...events, clock: events.clock ?? this.clock }) : undefined;
|
|
468
496
|
if (this.publisher) this.registerProcedure(this.publisher.procedure, { alwaysOn: true });
|
|
469
497
|
|
|
470
498
|
// Configure collections
|
|
@@ -794,6 +822,9 @@ export class ProcedureServer extends Server implements RepositoryProvider, StopP
|
|
|
794
822
|
}
|
|
795
823
|
const refusal = await this.admit(request);
|
|
796
824
|
if (refusal) return refusal;
|
|
825
|
+
// The limits come after the authentication, so a key can read the principal (CLI-40)
|
|
826
|
+
const limited = await this.limit(procedure, request);
|
|
827
|
+
if (limited) return limited;
|
|
797
828
|
this.calls++;
|
|
798
829
|
let response: ServerResponse<unknown> | undefined;
|
|
799
830
|
try {
|
|
@@ -869,6 +900,22 @@ export class ProcedureServer extends Server implements RepositoryProvider, StopP
|
|
|
869
900
|
return undefined;
|
|
870
901
|
}
|
|
871
902
|
|
|
903
|
+
/**
|
|
904
|
+
* The limits of a call (CLI-40): one unit of each limit that covers it. The function gives a
|
|
905
|
+
* `RATE_LIMITED` response, or undefined when the call passes. A key or a cover function that
|
|
906
|
+
* throws gives `INTERNAL_ERROR`, so a defect of a limit does not let a call in.
|
|
907
|
+
*/
|
|
908
|
+
private async limit(procedure: AnyProcedure, request: ServerRequest<unknown>): Promise<ServerResponse<unknown> | undefined> {
|
|
909
|
+
if (!this.callLimits) return undefined;
|
|
910
|
+
try {
|
|
911
|
+
const refused = await this.callLimits.admit(limitedCall(procedure, request));
|
|
912
|
+
return refused ? rateLimitedResponse(request.id, refused) : undefined;
|
|
913
|
+
} catch (error) {
|
|
914
|
+
const message = `The limits of the server cannot admit the call: ${error instanceof Error ? error.message : String(error)}`;
|
|
915
|
+
return errorResponse(request.id, Object.assign(new Error(message), { code: "INTERNAL_ERROR" }));
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
|
|
872
919
|
/** One call in flight ended: `drain()` can end when it was the last one. */
|
|
873
920
|
private callEnded(): void {
|
|
874
921
|
this.calls--;
|
|
@@ -1107,6 +1154,15 @@ export class ProcedureServer extends Server implements RepositoryProvider, StopP
|
|
|
1107
1154
|
return this.resolveProcedure(method);
|
|
1108
1155
|
}
|
|
1109
1156
|
|
|
1157
|
+
/**
|
|
1158
|
+
* This method gives the keyed limiters of the option `limits`, in its sequence (CLI-40). A host
|
|
1159
|
+
* declares each one on its service with `svc.limiter(limiter.name, limiter.spec)`, so
|
|
1160
|
+
* `describe()` and `lint()` see it.
|
|
1161
|
+
*/
|
|
1162
|
+
limiters(): readonly KeyedLimiter[] {
|
|
1163
|
+
return this.callLimits?.limiters ?? [];
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1110
1166
|
/**
|
|
1111
1167
|
* This function publishes an event on the bus of the server: the bus of the option `events`, or
|
|
1112
1168
|
* the global bus. Each subscription of the topic gets it (`documentation/EVENTS.md`).
|
|
@@ -40,9 +40,26 @@ export function withoutSecretHeaders(headers: Record<string, unknown>, secret?:
|
|
|
40
40
|
return out;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
-
/** The
|
|
43
|
+
/** The start of the header that `bearerOf` read before CLI-60: the scheme `Bearer` and one space. */
|
|
44
|
+
const BEARER_PREFIX = "Bearer ";
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The other forms of a bearer credential (RFC 7235 and RFC 6750, CLI-60). Such a form has the
|
|
48
|
+
* scheme `Bearer` in any case, one or more spaces (`1*SP`) and the token. Spaces or tabs can come
|
|
49
|
+
* after the token. The token has no whitespace (`token68`). A tab is not `1*SP`.
|
|
50
|
+
*/
|
|
51
|
+
const BEARER_CREDENTIAL = /^bearer +(\S+)[ \t]*$/i;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The token of an `Authorization: Bearer <token>` header, or undefined. A header that starts
|
|
55
|
+
* with `Bearer ` gives the rest of the header, as before CLI-60. A header with the scheme in
|
|
56
|
+
* another case, for example `bearer`, gives the token after one or more spaces.
|
|
57
|
+
*/
|
|
44
58
|
export function bearerOf(header: unknown): string | undefined {
|
|
45
|
-
|
|
59
|
+
if (typeof header !== "string") return undefined;
|
|
60
|
+
// The old form keeps its old result, so each header that passed before gives the same token
|
|
61
|
+
if (header.startsWith(BEARER_PREFIX)) return header.slice(BEARER_PREFIX.length);
|
|
62
|
+
return BEARER_CREDENTIAL.exec(header)?.[1];
|
|
46
63
|
}
|
|
47
64
|
|
|
48
65
|
/**
|