@shiprev/server-sdk-node 0.0.0-stage → 0.1.0-beta.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.
Files changed (45) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +276 -2
  3. package/dist/auth/token-verifier.d.ts +56 -0
  4. package/dist/auth/token-verifier.js +257 -0
  5. package/dist/client/connection-supervisor.d.ts +148 -0
  6. package/dist/client/connection-supervisor.js +650 -0
  7. package/dist/client/shiprev-client.d.ts +139 -0
  8. package/dist/client/shiprev-client.js +400 -0
  9. package/dist/index.d.ts +16 -0
  10. package/dist/index.js +47 -0
  11. package/dist/metering/backlog/host-adapter-backlog.d.ts +10 -0
  12. package/dist/metering/backlog/host-adapter-backlog.js +27 -0
  13. package/dist/metering/backlog/journal-backlog.d.ts +14 -0
  14. package/dist/metering/backlog/journal-backlog.js +135 -0
  15. package/dist/metering/backlog/memory-backlog.d.ts +9 -0
  16. package/dist/metering/backlog/memory-backlog.js +34 -0
  17. package/dist/metering/usage-buffer.d.ts +59 -0
  18. package/dist/metering/usage-buffer.js +175 -0
  19. package/dist/mirror/degradation.d.ts +22 -0
  20. package/dist/mirror/degradation.js +67 -0
  21. package/dist/mirror/entitlement-mirror.d.ts +61 -0
  22. package/dist/mirror/entitlement-mirror.js +143 -0
  23. package/dist/nest/decorators.d.ts +15 -0
  24. package/dist/nest/decorators.js +21 -0
  25. package/dist/nest/index.d.ts +4 -0
  26. package/dist/nest/index.js +21 -0
  27. package/dist/nest/shiprev-auth.guard.d.ts +7 -0
  28. package/dist/nest/shiprev-auth.guard.js +52 -0
  29. package/dist/nest/shiprev-capability.guard.d.ts +9 -0
  30. package/dist/nest/shiprev-capability.guard.js +65 -0
  31. package/dist/nest/shiprev.module.d.ts +17 -0
  32. package/dist/nest/shiprev.module.js +57 -0
  33. package/dist/ruling/adjudicator.d.ts +15 -0
  34. package/dist/ruling/adjudicator.js +123 -0
  35. package/dist/transport/event-stream.d.ts +27 -0
  36. package/dist/transport/event-stream.js +92 -0
  37. package/dist/transport/http.transport.d.ts +69 -0
  38. package/dist/transport/http.transport.js +238 -0
  39. package/dist/transport/wire.d.ts +79 -0
  40. package/dist/transport/wire.js +8 -0
  41. package/dist/types.d.ts +252 -0
  42. package/dist/types.js +37 -0
  43. package/dist/util/decimal.d.ts +14 -0
  44. package/dist/util/decimal.js +57 -0
  45. package/package.json +96 -3
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.EntitlementMirror = exports.MAX_SUPPORTED_SCHEMA_VERSION = void 0;
4
+ exports.MAX_SUPPORTED_SCHEMA_VERSION = 1;
5
+ /**
6
+ * In-memory replica of the application-level entitlement snapshot served by
7
+ * `GET /sdk/v1/entitlements/snapshot`, indexed by organization and capability.
8
+ */
9
+ class EntitlementMirror {
10
+ onChange;
11
+ organizations = new Map();
12
+ /** When each organization's entry was received (local clock). */
13
+ receivedAt = new Map();
14
+ currentSchemaVersion = exports.MAX_SUPPORTED_SCHEMA_VERSION;
15
+ projectId = '';
16
+ lastUpdatedAt = 0;
17
+ /**
18
+ * @param onChange called after the mirror changed: with the organizations whose entry was replaced or
19
+ * removed, or with `null` when the whole mirror was replaced.
20
+ */
21
+ constructor(onChange) {
22
+ this.onChange = onChange;
23
+ }
24
+ /**
25
+ * Replaces the mirror with an application-level snapshot envelope.
26
+ * If schema_version is higher than supported, the update is rejected and the existing mirror is retained.
27
+ */
28
+ loadSnapshot(snapshot) {
29
+ if (!this.acceptsEnvelope(snapshot)) {
30
+ return false;
31
+ }
32
+ const now = Date.now();
33
+ this.organizations.clear();
34
+ this.receivedAt.clear();
35
+ this.projectId = snapshot.project_id;
36
+ this.currentSchemaVersion = snapshot.schema_version;
37
+ for (const [organizationId, organization] of Object.entries(snapshot.organizations ?? {})) {
38
+ this.organizations.set(organizationId, organization);
39
+ this.receivedAt.set(organizationId, now);
40
+ }
41
+ this.lastUpdatedAt = now;
42
+ this.onChange?.(null);
43
+ return true;
44
+ }
45
+ /**
46
+ * Merges the organizations of a (typically single-organization) snapshot envelope into the mirror,
47
+ * leaving every other organization untouched. Used to refill a mirror miss.
48
+ */
49
+ mergeSnapshot(snapshot) {
50
+ if (!this.acceptsEnvelope(snapshot)) {
51
+ return false;
52
+ }
53
+ if (this.projectId && snapshot.project_id !== this.projectId) {
54
+ return false;
55
+ }
56
+ const now = Date.now();
57
+ const changed = [];
58
+ this.projectId = snapshot.project_id;
59
+ for (const [organizationId, organization] of Object.entries(snapshot.organizations ?? {})) {
60
+ const known = this.organizations.get(organizationId);
61
+ if (!known || organization.revision >= known.revision) {
62
+ this.organizations.set(organizationId, organization);
63
+ this.receivedAt.set(organizationId, now);
64
+ changed.push(organizationId);
65
+ }
66
+ }
67
+ this.lastUpdatedAt = now;
68
+ if (changed.length > 0)
69
+ this.onChange?.(changed);
70
+ return true;
71
+ }
72
+ /**
73
+ * Removes an organization, for a refill whose envelope no longer holds it (unsubscribed or deleted).
74
+ */
75
+ removeOrganization(organizationId) {
76
+ if (this.organizations.delete(organizationId)) {
77
+ this.receivedAt.delete(organizationId);
78
+ this.lastUpdatedAt = Date.now();
79
+ this.onChange?.([organizationId]);
80
+ }
81
+ }
82
+ /** The entry of one organization, or `undefined` when the mirror does not hold it. */
83
+ getOrganization(organizationId) {
84
+ return this.organizations.get(organizationId);
85
+ }
86
+ /** When the entry of one organization was received (local clock), or `undefined`. */
87
+ getReceivedAt(organizationId) {
88
+ return this.receivedAt.get(organizationId);
89
+ }
90
+ /** The organizations the mirror holds. */
91
+ organizationIds() {
92
+ return [...this.organizations.keys()];
93
+ }
94
+ /**
95
+ * Revision of one organization, or `undefined` when the mirror does not hold it.
96
+ */
97
+ getOrganizationRevision(organizationId) {
98
+ return this.organizations.get(organizationId)?.revision;
99
+ }
100
+ /**
101
+ * Looks up the snapshot item of a capability for an organization.
102
+ */
103
+ getItem(organizationId, capabilityKey) {
104
+ return this.organizations.get(organizationId)?.items?.[capabilityKey];
105
+ }
106
+ /**
107
+ * Checks whether the organization exists in the mirror.
108
+ */
109
+ hasOrganization(organizationId) {
110
+ return this.organizations.has(organizationId);
111
+ }
112
+ /**
113
+ * Highest organization revision in the mirror (0 when empty).
114
+ */
115
+ getRevision() {
116
+ let highest = 0;
117
+ for (const organization of this.organizations.values()) {
118
+ highest = Math.max(highest, organization.revision);
119
+ }
120
+ return highest;
121
+ }
122
+ getSchemaVersion() {
123
+ return this.currentSchemaVersion;
124
+ }
125
+ getProjectId() {
126
+ return this.projectId;
127
+ }
128
+ getLastUpdatedAt() {
129
+ return this.lastUpdatedAt;
130
+ }
131
+ isEmpty() {
132
+ return this.organizations.size === 0;
133
+ }
134
+ acceptsEnvelope(snapshot) {
135
+ if (!snapshot || typeof snapshot.schema_version !== 'number') {
136
+ throw new Error('Invalid snapshot structure: missing schema_version');
137
+ }
138
+ // A newer schema is refused to protect the host process; the existing mirror is kept.
139
+ return snapshot.schema_version <= exports.MAX_SUPPORTED_SCHEMA_VERSION;
140
+ }
141
+ }
142
+ exports.EntitlementMirror = EntitlementMirror;
143
+ //# sourceMappingURL=entitlement-mirror.js.map
@@ -0,0 +1,15 @@
1
+ export declare const SHIPREV_CLIENT = "SHIPREV_CLIENT";
2
+ export declare const SHIPREV_CAPABILITY_METADATA = "SHIPREV_CAPABILITY_METADATA";
3
+ export interface CapabilityRequirement {
4
+ capabilityKey: string;
5
+ operation: 'read' | 'write';
6
+ quantity?: number;
7
+ }
8
+ /**
9
+ * Injects the ShipRevClient instance in NestJS services / controllers
10
+ */
11
+ export declare const InjectShipRevClient: () => PropertyDecorator & ParameterDecorator;
12
+ /**
13
+ * Declares capability requirement for a controller route handler.
14
+ */
15
+ export declare const RequireCapability: (capabilityKey: string, operation?: "read" | "write", quantity?: number) => import("@nestjs/common").CustomDecorator<string>;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.RequireCapability = exports.InjectShipRevClient = exports.SHIPREV_CAPABILITY_METADATA = exports.SHIPREV_CLIENT = void 0;
4
+ const common_1 = require("@nestjs/common");
5
+ exports.SHIPREV_CLIENT = 'SHIPREV_CLIENT';
6
+ exports.SHIPREV_CAPABILITY_METADATA = 'SHIPREV_CAPABILITY_METADATA';
7
+ /**
8
+ * Injects the ShipRevClient instance in NestJS services / controllers
9
+ */
10
+ const InjectShipRevClient = () => (0, common_1.Inject)(exports.SHIPREV_CLIENT);
11
+ exports.InjectShipRevClient = InjectShipRevClient;
12
+ /**
13
+ * Declares capability requirement for a controller route handler.
14
+ */
15
+ const RequireCapability = (capabilityKey, operation = 'read', quantity = 1) => (0, common_1.SetMetadata)(exports.SHIPREV_CAPABILITY_METADATA, {
16
+ capabilityKey,
17
+ operation,
18
+ quantity,
19
+ });
20
+ exports.RequireCapability = RequireCapability;
21
+ //# sourceMappingURL=decorators.js.map
@@ -0,0 +1,4 @@
1
+ export * from './decorators';
2
+ export * from './shiprev.module';
3
+ export * from './shiprev-auth.guard';
4
+ export * from './shiprev-capability.guard';
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./decorators"), exports);
18
+ __exportStar(require("./shiprev.module"), exports);
19
+ __exportStar(require("./shiprev-auth.guard"), exports);
20
+ __exportStar(require("./shiprev-capability.guard"), exports);
21
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ import { CanActivate, ExecutionContext } from '@nestjs/common';
2
+ import { ShipRevClient } from '../client/shiprev-client';
3
+ export declare class ShipRevAuthGuard implements CanActivate {
4
+ private readonly client;
5
+ constructor(client: ShipRevClient);
6
+ canActivate(context: ExecutionContext): Promise<boolean>;
7
+ }
@@ -0,0 +1,52 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.ShipRevAuthGuard = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const shiprev_client_1 = require("../client/shiprev-client");
18
+ const decorators_1 = require("./decorators");
19
+ let ShipRevAuthGuard = class ShipRevAuthGuard {
20
+ client;
21
+ constructor(client) {
22
+ this.client = client;
23
+ }
24
+ async canActivate(context) {
25
+ const request = context.switchToHttp().getRequest();
26
+ const authHeader = request.headers?.authorization;
27
+ if (!authHeader || typeof authHeader !== 'string') {
28
+ throw new common_1.UnauthorizedException('Missing Authorization header');
29
+ }
30
+ const [scheme, token] = authHeader.split(' ');
31
+ if (scheme?.toLowerCase() !== 'bearer' || !token) {
32
+ throw new common_1.UnauthorizedException('Invalid Bearer token format');
33
+ }
34
+ try {
35
+ const claims = await this.client.verifyToken(token);
36
+ request.authClaims = claims;
37
+ request.user = claims;
38
+ return true;
39
+ }
40
+ catch (err) {
41
+ const message = err instanceof Error ? err.message : 'Invalid or expired token';
42
+ throw new common_1.UnauthorizedException(`Token verification failed: ${message}`);
43
+ }
44
+ }
45
+ };
46
+ exports.ShipRevAuthGuard = ShipRevAuthGuard;
47
+ exports.ShipRevAuthGuard = ShipRevAuthGuard = __decorate([
48
+ (0, common_1.Injectable)(),
49
+ __param(0, (0, common_1.Inject)(decorators_1.SHIPREV_CLIENT)),
50
+ __metadata("design:paramtypes", [shiprev_client_1.ShipRevClient])
51
+ ], ShipRevAuthGuard);
52
+ //# sourceMappingURL=shiprev-auth.guard.js.map
@@ -0,0 +1,9 @@
1
+ import { CanActivate, ExecutionContext } from '@nestjs/common';
2
+ import { Reflector } from '@nestjs/core';
3
+ import { ShipRevClient } from '../client/shiprev-client';
4
+ export declare class ShipRevCapabilityGuard implements CanActivate {
5
+ private readonly reflector;
6
+ private readonly client;
7
+ constructor(reflector: Reflector, client: ShipRevClient);
8
+ canActivate(context: ExecutionContext): Promise<boolean>;
9
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var __metadata = (this && this.__metadata) || function (k, v) {
9
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
10
+ };
11
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
12
+ return function (target, key) { decorator(target, key, paramIndex); }
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.ShipRevCapabilityGuard = void 0;
16
+ const common_1 = require("@nestjs/common");
17
+ const core_1 = require("@nestjs/core");
18
+ const shiprev_client_1 = require("../client/shiprev-client");
19
+ const decorators_1 = require("./decorators");
20
+ let ShipRevCapabilityGuard = class ShipRevCapabilityGuard {
21
+ reflector;
22
+ client;
23
+ constructor(reflector, client) {
24
+ this.reflector = reflector;
25
+ this.client = client;
26
+ }
27
+ async canActivate(context) {
28
+ const requirement = this.reflector.getAllAndOverride(decorators_1.SHIPREV_CAPABILITY_METADATA, [context.getHandler(), context.getClass()]);
29
+ // If route doesn't require capability check, allow
30
+ if (!requirement) {
31
+ return true;
32
+ }
33
+ const request = context.switchToHttp().getRequest();
34
+ // The organization comes only from the verified token. Headers, route parameters and the body
35
+ // are under the caller's control, so falling back to them would let a signed-in user rule and
36
+ // meter against another organization. No organization in the token is a refusal.
37
+ const claims = request.authClaims || request.user;
38
+ const organizationId = claims?.organization_id;
39
+ if (typeof organizationId !== 'string' || organizationId === '') {
40
+ throw new common_1.ForbiddenException(`Capability ${requirement.capabilityKey} not allowed: missing_organization`);
41
+ }
42
+ const ruling = await this.client.adjudicateAsync({
43
+ organization_id: organizationId,
44
+ capability_key: requirement.capabilityKey,
45
+ operation: requirement.operation,
46
+ quantity: requirement.quantity ?? 1,
47
+ });
48
+ if (!ruling.allowed) {
49
+ if (ruling.reason === 'disconnected_write') {
50
+ throw new common_1.ForbiddenException('Write operation denied while client is disconnected');
51
+ }
52
+ throw new common_1.ForbiddenException(`Capability ${requirement.capabilityKey} not allowed: ${ruling.reason ?? 'denied'}`);
53
+ }
54
+ request.ruling = ruling;
55
+ return true;
56
+ }
57
+ };
58
+ exports.ShipRevCapabilityGuard = ShipRevCapabilityGuard;
59
+ exports.ShipRevCapabilityGuard = ShipRevCapabilityGuard = __decorate([
60
+ (0, common_1.Injectable)(),
61
+ __param(1, (0, common_1.Inject)(decorators_1.SHIPREV_CLIENT)),
62
+ __metadata("design:paramtypes", [core_1.Reflector,
63
+ shiprev_client_1.ShipRevClient])
64
+ ], ShipRevCapabilityGuard);
65
+ //# sourceMappingURL=shiprev-capability.guard.js.map
@@ -0,0 +1,17 @@
1
+ import { DynamicModule, FactoryProvider, ModuleMetadata } from '@nestjs/common';
2
+ import { ShipRevClientOptions } from '../types';
3
+ export interface ShipRevModuleAsyncOptions {
4
+ /** Modules that export what `useFactory` injects, for example the host's `ConfigModule`. */
5
+ imports?: ModuleMetadata['imports'];
6
+ /** Providers passed to `useFactory`, in order. */
7
+ inject?: FactoryProvider['inject'];
8
+ /**
9
+ * Builds the client options from the injected providers. Declared as a method so that a factory
10
+ * typed with the injected classes, `(config: ConfigService) => …`, is assignable as written.
11
+ */
12
+ useFactory(...args: unknown[]): Promise<ShipRevClientOptions> | ShipRevClientOptions;
13
+ }
14
+ export declare class ShipRevModule {
15
+ static forRoot(options: ShipRevClientOptions): DynamicModule;
16
+ static forRootAsync(asyncOptions: ShipRevModuleAsyncOptions): DynamicModule;
17
+ }
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
3
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
4
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
5
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
6
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
7
+ };
8
+ var ShipRevModule_1;
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.ShipRevModule = void 0;
11
+ const common_1 = require("@nestjs/common");
12
+ const shiprev_client_1 = require("../client/shiprev-client");
13
+ const decorators_1 = require("./decorators");
14
+ const shiprev_auth_guard_1 = require("./shiprev-auth.guard");
15
+ const shiprev_capability_guard_1 = require("./shiprev-capability.guard");
16
+ let ShipRevModule = ShipRevModule_1 = class ShipRevModule {
17
+ static forRoot(options) {
18
+ const clientProvider = {
19
+ provide: decorators_1.SHIPREV_CLIENT,
20
+ useFactory: () => {
21
+ const client = new shiprev_client_1.ShipRevClient(options);
22
+ // Start client in background (non-blocking for host boot)
23
+ void client.start();
24
+ return client;
25
+ },
26
+ };
27
+ return {
28
+ module: ShipRevModule_1,
29
+ providers: [clientProvider, shiprev_auth_guard_1.ShipRevAuthGuard, shiprev_capability_guard_1.ShipRevCapabilityGuard],
30
+ exports: [clientProvider, shiprev_auth_guard_1.ShipRevAuthGuard, shiprev_capability_guard_1.ShipRevCapabilityGuard],
31
+ };
32
+ }
33
+ static forRootAsync(asyncOptions) {
34
+ const clientProvider = {
35
+ provide: decorators_1.SHIPREV_CLIENT,
36
+ useFactory: async (...args) => {
37
+ const options = await asyncOptions.useFactory(...args);
38
+ const client = new shiprev_client_1.ShipRevClient(options);
39
+ void client.start();
40
+ return client;
41
+ },
42
+ inject: asyncOptions.inject ?? [],
43
+ };
44
+ return {
45
+ module: ShipRevModule_1,
46
+ imports: asyncOptions.imports ?? [],
47
+ providers: [clientProvider, shiprev_auth_guard_1.ShipRevAuthGuard, shiprev_capability_guard_1.ShipRevCapabilityGuard],
48
+ exports: [clientProvider, shiprev_auth_guard_1.ShipRevAuthGuard, shiprev_capability_guard_1.ShipRevCapabilityGuard],
49
+ };
50
+ }
51
+ };
52
+ exports.ShipRevModule = ShipRevModule;
53
+ exports.ShipRevModule = ShipRevModule = ShipRevModule_1 = __decorate([
54
+ (0, common_1.Global)(),
55
+ (0, common_1.Module)({})
56
+ ], ShipRevModule);
57
+ //# sourceMappingURL=shiprev.module.js.map
@@ -0,0 +1,15 @@
1
+ import { EntitlementMirror } from '../mirror/entitlement-mirror';
2
+ import { AdjudicateOptions, ClientPresets, Ruling } from '../types';
3
+ export declare class Adjudicator {
4
+ private readonly mirror;
5
+ private readonly presets?;
6
+ constructor(mirror: EntitlementMirror, presets?: ClientPresets | undefined);
7
+ /**
8
+ * Synthesizes a four-tier ruling based on local mirror and connection state.
9
+ *
10
+ * @param options Adjudication parameters including explicit operation ('read' | 'write')
11
+ * @param isOnline Whether the connection to the control plane is currently healthy
12
+ * @param authoritativeLookup Optional callback to synchronously fetch from remote if missing in mirror
13
+ */
14
+ adjudicate(options: AdjudicateOptions, isOnline: boolean, authoritativeLookup?: () => Ruling | undefined): Ruling;
15
+ }
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.Adjudicator = void 0;
4
+ const decimal_1 = require("../util/decimal");
5
+ class Adjudicator {
6
+ mirror;
7
+ presets;
8
+ constructor(mirror, presets) {
9
+ this.mirror = mirror;
10
+ this.presets = presets;
11
+ }
12
+ /**
13
+ * Synthesizes a four-tier ruling based on local mirror and connection state.
14
+ *
15
+ * @param options Adjudication parameters including explicit operation ('read' | 'write')
16
+ * @param isOnline Whether the connection to the control plane is currently healthy
17
+ * @param authoritativeLookup Optional callback to synchronously fetch from remote if missing in mirror
18
+ */
19
+ adjudicate(options, isOnline, authoritativeLookup) {
20
+ const now = Date.now();
21
+ // 1. Mandatory operation guard
22
+ if (options.operation !== 'read' && options.operation !== 'write') {
23
+ return {
24
+ allowed: false,
25
+ source: 'client_default',
26
+ reason: 'invalid_request',
27
+ adjudicated_at: now,
28
+ };
29
+ }
30
+ const { organization_id, capability_key, quantity = 1, operation } = options;
31
+ if (typeof quantity !== 'number' || !Number.isFinite(quantity) || quantity < 0) {
32
+ return {
33
+ allowed: false,
34
+ source: 'client_default',
35
+ reason: 'invalid_request',
36
+ adjudicated_at: now,
37
+ };
38
+ }
39
+ // 2. Cold start offline (mirror is empty and control plane is offline)
40
+ if (this.mirror.isEmpty() && !isOnline) {
41
+ if (operation === 'write') {
42
+ return {
43
+ allowed: false,
44
+ source: 'client_default',
45
+ reason: 'disconnected_write',
46
+ adjudicated_at: now,
47
+ };
48
+ }
49
+ // Read operation: check customer preset
50
+ const allowColdRead = this.presets?.cold_start === 'allow';
51
+ return {
52
+ allowed: allowColdRead,
53
+ source: 'client_default',
54
+ reason: allowColdRead ? undefined : 'cold_start_denied',
55
+ adjudicated_at: now,
56
+ };
57
+ }
58
+ // 3. Disconnected mode with existing mirror
59
+ if (!isOnline) {
60
+ if (operation === 'write') {
61
+ return {
62
+ allowed: false,
63
+ source: 'degraded',
64
+ reason: 'disconnected_write',
65
+ adjudicated_at: now,
66
+ };
67
+ }
68
+ // Read fail-open under disconnected state
69
+ return {
70
+ allowed: true,
71
+ source: 'degraded',
72
+ adjudicated_at: now,
73
+ };
74
+ }
75
+ // 4. Online evaluation: check mirror first
76
+ const item = this.mirror.getItem(organization_id, capability_key);
77
+ if (item) {
78
+ // Tier 1: realtime, compared exactly on the snapshot's decimal strings
79
+ const limit = (0, decimal_1.decimalToNumber)(item.allowance);
80
+ if ((0, decimal_1.compareDecimal)((0, decimal_1.toDecimalString)(quantity), item.remaining) <= 0) {
81
+ return {
82
+ allowed: true,
83
+ remaining: (0, decimal_1.decimalToNumber)(item.remaining),
84
+ limit,
85
+ source: 'realtime',
86
+ adjudicated_at: now,
87
+ };
88
+ }
89
+ // Beyond the remaining allowance the call is not decided here, whatever the policy: whether credits (and, for
90
+ // ALLOW_AND_BILL, an overdraft) cover it is state the mirror does not hold. The local answer is a refusal;
91
+ // adjudicateAsync() asks the service on it, and without an answer the call stays refused (fail closed).
92
+ return {
93
+ allowed: false,
94
+ remaining: (0, decimal_1.decimalToNumber)(item.remaining),
95
+ limit,
96
+ source: 'realtime',
97
+ reason: 'quota_exhausted',
98
+ adjudicated_at: now,
99
+ };
100
+ }
101
+ // 5. Tier 2: authoritative fallback if missing in mirror but online
102
+ if (authoritativeLookup) {
103
+ const authoritativeResult = authoritativeLookup();
104
+ if (authoritativeResult) {
105
+ return {
106
+ ...authoritativeResult,
107
+ source: 'authoritative',
108
+ adjudicated_at: now,
109
+ };
110
+ }
111
+ }
112
+ // Online but the mirror does not know the organization or capability: a client-side refusal,
113
+ // not a service ruling. adjudicateAsync() asks the service on this reason.
114
+ return {
115
+ allowed: false,
116
+ source: 'client_default',
117
+ reason: 'capability_not_granted',
118
+ adjudicated_at: now,
119
+ };
120
+ }
121
+ }
122
+ exports.Adjudicator = Adjudicator;
123
+ //# sourceMappingURL=adjudicator.js.map
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Server-Sent Events reader for `GET /sdk/v1/events`, on top of a `fetch` response body. No
3
+ * dependency: the frames are parsed here.
4
+ *
5
+ * Accepts what the format allows: frames split across reads or several per read, LF, CRLF or CR
6
+ * line endings, comment lines (starting with `:`), and `data` spread over several lines (joined
7
+ * with a newline). Fields other than `event` and `data` are ignored.
8
+ */
9
+ export interface EventStreamFrame {
10
+ /** Event type; `message` when the frame names none. */
11
+ event: string;
12
+ /** The `data` lines joined with `\n`. */
13
+ data: string;
14
+ }
15
+ /** Incremental parser: feed it text, it returns the frames completed so far. */
16
+ export declare class EventStreamParser {
17
+ private pending;
18
+ private eventType;
19
+ private dataLines;
20
+ push(chunk: string): EventStreamFrame[];
21
+ private line;
22
+ }
23
+ /**
24
+ * Reads the body of an event stream response and hands every frame to `onFrame`. Resolves when the
25
+ * body ends, rejects on a read error (a reset connection, an abort).
26
+ */
27
+ export declare function readEventStream(body: ReadableStream<Uint8Array>, onFrame: (frame: EventStreamFrame) => void): Promise<void>;