@shiprev/server-sdk-node 0.0.0-stage → 0.1.0-beta.0
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/LICENSE +202 -0
- package/README.md +253 -2
- package/dist/auth/token-verifier.d.ts +42 -0
- package/dist/auth/token-verifier.js +230 -0
- package/dist/client/connection-supervisor.d.ts +117 -0
- package/dist/client/connection-supervisor.js +547 -0
- package/dist/client/shiprev-client.d.ts +106 -0
- package/dist/client/shiprev-client.js +267 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +47 -0
- package/dist/metering/backlog/host-adapter-backlog.d.ts +10 -0
- package/dist/metering/backlog/host-adapter-backlog.js +27 -0
- package/dist/metering/backlog/journal-backlog.d.ts +14 -0
- package/dist/metering/backlog/journal-backlog.js +135 -0
- package/dist/metering/backlog/memory-backlog.d.ts +9 -0
- package/dist/metering/backlog/memory-backlog.js +34 -0
- package/dist/metering/usage-buffer.d.ts +59 -0
- package/dist/metering/usage-buffer.js +175 -0
- package/dist/mirror/entitlement-mirror.d.ts +47 -0
- package/dist/mirror/entitlement-mirror.js +109 -0
- package/dist/nest/decorators.d.ts +15 -0
- package/dist/nest/decorators.js +21 -0
- package/dist/nest/index.d.ts +4 -0
- package/dist/nest/index.js +21 -0
- package/dist/nest/shiprev-auth.guard.d.ts +7 -0
- package/dist/nest/shiprev-auth.guard.js +52 -0
- package/dist/nest/shiprev-capability.guard.d.ts +9 -0
- package/dist/nest/shiprev-capability.guard.js +65 -0
- package/dist/nest/shiprev.module.d.ts +17 -0
- package/dist/nest/shiprev.module.js +57 -0
- package/dist/ruling/adjudicator.d.ts +15 -0
- package/dist/ruling/adjudicator.js +131 -0
- package/dist/transport/event-stream.d.ts +27 -0
- package/dist/transport/event-stream.js +92 -0
- package/dist/transport/http.transport.d.ts +64 -0
- package/dist/transport/http.transport.js +218 -0
- package/dist/transport/wire.d.ts +79 -0
- package/dist/transport/wire.js +8 -0
- package/dist/types.d.ts +191 -0
- package/dist/types.js +25 -0
- package/dist/util/decimal.d.ts +14 -0
- package/dist/util/decimal.js +57 -0
- package/package.json +98 -4
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UsageBuffer = void 0;
|
|
4
|
+
const host_adapter_backlog_1 = require("./backlog/host-adapter-backlog");
|
|
5
|
+
const journal_backlog_1 = require("./backlog/journal-backlog");
|
|
6
|
+
const memory_backlog_1 = require("./backlog/memory-backlog");
|
|
7
|
+
class UsageBuffer {
|
|
8
|
+
transport;
|
|
9
|
+
getToken;
|
|
10
|
+
isOnline;
|
|
11
|
+
send;
|
|
12
|
+
batchSize;
|
|
13
|
+
flushIntervalMs;
|
|
14
|
+
backlog;
|
|
15
|
+
onFlushFailure;
|
|
16
|
+
onRejected;
|
|
17
|
+
buffer = [];
|
|
18
|
+
timer = null;
|
|
19
|
+
/** The pass in flight, if any; at most one runs at a time. */
|
|
20
|
+
inFlight = null;
|
|
21
|
+
isStopped = false;
|
|
22
|
+
constructor(options) {
|
|
23
|
+
this.transport = options.transport;
|
|
24
|
+
this.getToken = options.getToken;
|
|
25
|
+
this.isOnline = options.isOnline;
|
|
26
|
+
this.send = options.send;
|
|
27
|
+
this.batchSize = options.bufferConfig?.batch_size ?? 100;
|
|
28
|
+
this.flushIntervalMs = options.bufferConfig?.flush_interval_ms ?? 5000;
|
|
29
|
+
this.onFlushFailure = options.onFlushFailure;
|
|
30
|
+
this.onRejected = options.onRejected;
|
|
31
|
+
// Initialize backlog adapter based on mode
|
|
32
|
+
const mode = options.backlogConfig?.mode ?? 'memory';
|
|
33
|
+
if (mode === 'journal') {
|
|
34
|
+
this.backlog = new journal_backlog_1.JournalBacklog(options.backlogConfig?.journal_path);
|
|
35
|
+
}
|
|
36
|
+
else if (mode === 'host_adapter') {
|
|
37
|
+
this.backlog = new host_adapter_backlog_1.HostAdapterBacklog(options.backlogConfig?.adapter);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
this.backlog = new memory_backlog_1.MemoryBacklog();
|
|
41
|
+
}
|
|
42
|
+
this.startTimer();
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Appends an event to the buffer. If buffer reaches batchSize, triggers flush.
|
|
46
|
+
*/
|
|
47
|
+
async reportUsage(event) {
|
|
48
|
+
if (this.isStopped) {
|
|
49
|
+
throw new Error('UsageBuffer is stopped, cannot report new events');
|
|
50
|
+
}
|
|
51
|
+
this.buffer.push(event);
|
|
52
|
+
if (this.buffer.length >= this.batchSize) {
|
|
53
|
+
await this.flush();
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Delivers every usage event reported before this call: the backlog first, in batches, then the
|
|
58
|
+
* in-memory buffer. A flush already in flight is awaited first, then this call runs its own pass.
|
|
59
|
+
* Resolves once each of those events was accepted, refused (`onRejected`), or moved to the
|
|
60
|
+
* backlog because it could not be delivered (offline, no token, or a failed request).
|
|
61
|
+
*/
|
|
62
|
+
async flush() {
|
|
63
|
+
while (this.inFlight) {
|
|
64
|
+
await this.inFlight.catch(() => undefined);
|
|
65
|
+
}
|
|
66
|
+
await this.runPass(true);
|
|
67
|
+
}
|
|
68
|
+
runPass(drainBacklog) {
|
|
69
|
+
const pass = this.pass(drainBacklog).finally(() => {
|
|
70
|
+
if (this.inFlight === pass)
|
|
71
|
+
this.inFlight = null;
|
|
72
|
+
});
|
|
73
|
+
this.inFlight = pass;
|
|
74
|
+
return pass;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* One pass. With `drainBacklog` the backlog is sent in batches until it is empty or a batch
|
|
78
|
+
* fails; without it (timer ticks) at most one backlog batch is sent.
|
|
79
|
+
*/
|
|
80
|
+
async pass(drainBacklog) {
|
|
81
|
+
const token = this.getToken();
|
|
82
|
+
const online = this.isOnline();
|
|
83
|
+
// If offline or no token available, route memory events to backlog directly
|
|
84
|
+
if (!online || !token) {
|
|
85
|
+
if (this.buffer.length > 0) {
|
|
86
|
+
const eventsToBacklog = [...this.buffer];
|
|
87
|
+
this.buffer = [];
|
|
88
|
+
for (const ev of eventsToBacklog) {
|
|
89
|
+
await this.backlog.enqueue(ev);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
// 1. Drain pending backlog first; bounded by its size now, so later arrivals cannot extend it
|
|
95
|
+
const batches = drainBacklog ? Math.ceil((await this.backlog.size()) / this.batchSize) : 1;
|
|
96
|
+
for (let i = 0; i < batches; i++) {
|
|
97
|
+
if ((await this.flushBacklog(token)) !== 'delivered')
|
|
98
|
+
break;
|
|
99
|
+
}
|
|
100
|
+
// 2. Drain current memory buffer
|
|
101
|
+
if (this.buffer.length > 0) {
|
|
102
|
+
const eventsToSend = [...this.buffer];
|
|
103
|
+
this.buffer = [];
|
|
104
|
+
try {
|
|
105
|
+
const res = await this.deliver(eventsToSend, token);
|
|
106
|
+
if (res.rejected && res.rejected.length > 0) {
|
|
107
|
+
this.onRejected?.(res.rejected);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
catch (err) {
|
|
111
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
112
|
+
this.onFlushFailure?.(eventsToSend, error);
|
|
113
|
+
// Persist to backlog on failure
|
|
114
|
+
for (const ev of eventsToSend) {
|
|
115
|
+
await this.backlog.enqueue(ev);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async flushBacklog(token) {
|
|
121
|
+
const backlogItems = await this.backlog.peek(this.batchSize);
|
|
122
|
+
if (backlogItems.length === 0)
|
|
123
|
+
return 'empty';
|
|
124
|
+
const events = backlogItems.map((item) => item.event);
|
|
125
|
+
const ids = backlogItems.map((item) => item.id);
|
|
126
|
+
try {
|
|
127
|
+
const res = await this.deliver(events, token);
|
|
128
|
+
await this.backlog.ack(ids);
|
|
129
|
+
if (res.rejected && res.rejected.length > 0) {
|
|
130
|
+
this.onRejected?.(res.rejected);
|
|
131
|
+
}
|
|
132
|
+
return 'delivered';
|
|
133
|
+
}
|
|
134
|
+
catch {
|
|
135
|
+
await this.backlog.nack(ids);
|
|
136
|
+
return 'failed';
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
deliver(events, token) {
|
|
140
|
+
return this.send ? this.send(events) : this.transport.reportUsageEvents(events, token);
|
|
141
|
+
}
|
|
142
|
+
startTimer() {
|
|
143
|
+
if (this.flushIntervalMs <= 0)
|
|
144
|
+
return;
|
|
145
|
+
this.timer = setInterval(() => {
|
|
146
|
+
// A tick skips while a pass is in flight, and sends at most one backlog batch.
|
|
147
|
+
if (this.inFlight)
|
|
148
|
+
return;
|
|
149
|
+
void this.runPass(false);
|
|
150
|
+
}, this.flushIntervalMs);
|
|
151
|
+
// Don't keep Node process alive solely for background flush interval
|
|
152
|
+
if (this.timer.unref) {
|
|
153
|
+
this.timer.unref();
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Gracefully stops the buffer flusher and performs final drain.
|
|
158
|
+
*/
|
|
159
|
+
async stop() {
|
|
160
|
+
this.isStopped = true;
|
|
161
|
+
if (this.timer) {
|
|
162
|
+
clearInterval(this.timer);
|
|
163
|
+
this.timer = null;
|
|
164
|
+
}
|
|
165
|
+
await this.flush();
|
|
166
|
+
}
|
|
167
|
+
getBufferSize() {
|
|
168
|
+
return this.buffer.length;
|
|
169
|
+
}
|
|
170
|
+
async getBacklogSize() {
|
|
171
|
+
return this.backlog.size();
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
exports.UsageBuffer = UsageBuffer;
|
|
175
|
+
//# sourceMappingURL=usage-buffer.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Snapshot, SnapshotItem } from '../types';
|
|
2
|
+
export declare const MAX_SUPPORTED_SCHEMA_VERSION = 1;
|
|
3
|
+
/**
|
|
4
|
+
* In-memory replica of the application-level entitlement snapshot served by
|
|
5
|
+
* `GET /sdk/v1/entitlements/snapshot`, indexed by organization and capability.
|
|
6
|
+
*/
|
|
7
|
+
export declare class EntitlementMirror {
|
|
8
|
+
private readonly organizations;
|
|
9
|
+
private currentSchemaVersion;
|
|
10
|
+
private projectId;
|
|
11
|
+
private lastUpdatedAt;
|
|
12
|
+
/**
|
|
13
|
+
* Replaces the mirror with an application-level snapshot envelope.
|
|
14
|
+
* If schema_version is higher than supported, the update is rejected and the existing mirror is retained.
|
|
15
|
+
*/
|
|
16
|
+
loadSnapshot(snapshot: Snapshot): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Merges the organizations of a (typically single-organization) snapshot envelope into the mirror,
|
|
19
|
+
* leaving every other organization untouched. Used to refill a mirror miss.
|
|
20
|
+
*/
|
|
21
|
+
mergeSnapshot(snapshot: Snapshot): boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Removes an organization, for a refill whose envelope no longer holds it (unsubscribed or deleted).
|
|
24
|
+
*/
|
|
25
|
+
removeOrganization(organizationId: string): void;
|
|
26
|
+
/**
|
|
27
|
+
* Revision of one organization, or `undefined` when the mirror does not hold it.
|
|
28
|
+
*/
|
|
29
|
+
getOrganizationRevision(organizationId: string): number | undefined;
|
|
30
|
+
/**
|
|
31
|
+
* Looks up the snapshot item of a capability for an organization.
|
|
32
|
+
*/
|
|
33
|
+
getItem(organizationId: string, capabilityKey: string): SnapshotItem | undefined;
|
|
34
|
+
/**
|
|
35
|
+
* Checks whether the organization exists in the mirror.
|
|
36
|
+
*/
|
|
37
|
+
hasOrganization(organizationId: string): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Highest organization revision in the mirror (0 when empty).
|
|
40
|
+
*/
|
|
41
|
+
getRevision(): number;
|
|
42
|
+
getSchemaVersion(): number;
|
|
43
|
+
getProjectId(): string;
|
|
44
|
+
getLastUpdatedAt(): number;
|
|
45
|
+
isEmpty(): boolean;
|
|
46
|
+
private acceptsEnvelope;
|
|
47
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
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
|
+
organizations = new Map();
|
|
11
|
+
currentSchemaVersion = exports.MAX_SUPPORTED_SCHEMA_VERSION;
|
|
12
|
+
projectId = '';
|
|
13
|
+
lastUpdatedAt = 0;
|
|
14
|
+
/**
|
|
15
|
+
* Replaces the mirror with an application-level snapshot envelope.
|
|
16
|
+
* If schema_version is higher than supported, the update is rejected and the existing mirror is retained.
|
|
17
|
+
*/
|
|
18
|
+
loadSnapshot(snapshot) {
|
|
19
|
+
if (!this.acceptsEnvelope(snapshot)) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
this.organizations.clear();
|
|
23
|
+
this.projectId = snapshot.project_id;
|
|
24
|
+
this.currentSchemaVersion = snapshot.schema_version;
|
|
25
|
+
for (const [organizationId, organization] of Object.entries(snapshot.organizations ?? {})) {
|
|
26
|
+
this.organizations.set(organizationId, organization);
|
|
27
|
+
}
|
|
28
|
+
this.lastUpdatedAt = Date.now();
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Merges the organizations of a (typically single-organization) snapshot envelope into the mirror,
|
|
33
|
+
* leaving every other organization untouched. Used to refill a mirror miss.
|
|
34
|
+
*/
|
|
35
|
+
mergeSnapshot(snapshot) {
|
|
36
|
+
if (!this.acceptsEnvelope(snapshot)) {
|
|
37
|
+
return false;
|
|
38
|
+
}
|
|
39
|
+
if (this.projectId && snapshot.project_id !== this.projectId) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
this.projectId = snapshot.project_id;
|
|
43
|
+
for (const [organizationId, organization] of Object.entries(snapshot.organizations ?? {})) {
|
|
44
|
+
const known = this.organizations.get(organizationId);
|
|
45
|
+
if (!known || organization.revision >= known.revision) {
|
|
46
|
+
this.organizations.set(organizationId, organization);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
this.lastUpdatedAt = Date.now();
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Removes an organization, for a refill whose envelope no longer holds it (unsubscribed or deleted).
|
|
54
|
+
*/
|
|
55
|
+
removeOrganization(organizationId) {
|
|
56
|
+
if (this.organizations.delete(organizationId)) {
|
|
57
|
+
this.lastUpdatedAt = Date.now();
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Revision of one organization, or `undefined` when the mirror does not hold it.
|
|
62
|
+
*/
|
|
63
|
+
getOrganizationRevision(organizationId) {
|
|
64
|
+
return this.organizations.get(organizationId)?.revision;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Looks up the snapshot item of a capability for an organization.
|
|
68
|
+
*/
|
|
69
|
+
getItem(organizationId, capabilityKey) {
|
|
70
|
+
return this.organizations.get(organizationId)?.items?.[capabilityKey];
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* Checks whether the organization exists in the mirror.
|
|
74
|
+
*/
|
|
75
|
+
hasOrganization(organizationId) {
|
|
76
|
+
return this.organizations.has(organizationId);
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Highest organization revision in the mirror (0 when empty).
|
|
80
|
+
*/
|
|
81
|
+
getRevision() {
|
|
82
|
+
let highest = 0;
|
|
83
|
+
for (const organization of this.organizations.values()) {
|
|
84
|
+
highest = Math.max(highest, organization.revision);
|
|
85
|
+
}
|
|
86
|
+
return highest;
|
|
87
|
+
}
|
|
88
|
+
getSchemaVersion() {
|
|
89
|
+
return this.currentSchemaVersion;
|
|
90
|
+
}
|
|
91
|
+
getProjectId() {
|
|
92
|
+
return this.projectId;
|
|
93
|
+
}
|
|
94
|
+
getLastUpdatedAt() {
|
|
95
|
+
return this.lastUpdatedAt;
|
|
96
|
+
}
|
|
97
|
+
isEmpty() {
|
|
98
|
+
return this.organizations.size === 0;
|
|
99
|
+
}
|
|
100
|
+
acceptsEnvelope(snapshot) {
|
|
101
|
+
if (!snapshot || typeof snapshot.schema_version !== 'number') {
|
|
102
|
+
throw new Error('Invalid snapshot structure: missing schema_version');
|
|
103
|
+
}
|
|
104
|
+
// A newer schema is refused to protect the host process; the existing mirror is kept.
|
|
105
|
+
return snapshot.schema_version <= exports.MAX_SUPPORTED_SCHEMA_VERSION;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
exports.EntitlementMirror = EntitlementMirror;
|
|
109
|
+
//# 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,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
|
+
}
|