@usagekit/meter 0.4.0 → 0.5.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/dist/meter.js +71 -2
- package/package.json +3 -3
package/dist/meter.js
CHANGED
|
@@ -4,6 +4,7 @@ import { defaultPolicy } from "./policy.js";
|
|
|
4
4
|
import { validate, reserveValidation, usageValidation, operationsValidation, sourceValidation, provenanceValidation, requestCountsValidation, } from "./validation.js";
|
|
5
5
|
import { canRead, canReadBudget, canReadShared, isShared } from "./access.js";
|
|
6
6
|
import { defaultBudgetOrder, normalizeTags } from "@usagekit/core";
|
|
7
|
+
import { validateBillingImportInput, validateBillingImportsQuery } from "@usagekit/store";
|
|
7
8
|
export function createMeter({ store, policy = defaultPolicy, clock, resolveOwnership, catalog, resolveConnection, }) {
|
|
8
9
|
if (!Number.isFinite(clock.now().getTime()))
|
|
9
10
|
throw new Error("InvalidInput: clock");
|
|
@@ -30,8 +31,58 @@ export function createMeter({ store, policy = defaultPolicy, clock, resolveOwner
|
|
|
30
31
|
};
|
|
31
32
|
const read = async (fn) => safely(async () => ({ outcome: "ok", value: await fn() }));
|
|
32
33
|
return {
|
|
34
|
+
importBilling: async (access, i) => {
|
|
35
|
+
const invalid = validateBillingImportInput(i);
|
|
36
|
+
if (invalid)
|
|
37
|
+
return invalid;
|
|
38
|
+
if (access.canImportBilling !== true ||
|
|
39
|
+
!access.canReadBillingDetail ||
|
|
40
|
+
!canRead(access, {
|
|
41
|
+
kind: "principal",
|
|
42
|
+
namespace: i.scope.namespace,
|
|
43
|
+
principal: i.scope.principal,
|
|
44
|
+
}))
|
|
45
|
+
return { outcome: "forbidden" };
|
|
46
|
+
const owner = await resolveOwnership?.({
|
|
47
|
+
kind: "connection",
|
|
48
|
+
namespace: i.scope.namespace,
|
|
49
|
+
connection: i.scope.connection,
|
|
50
|
+
});
|
|
51
|
+
if (!owner ||
|
|
52
|
+
(owner.kind === "principal"
|
|
53
|
+
? owner.principal !== i.scope.principal
|
|
54
|
+
: owner.group !== i.scope.group) ||
|
|
55
|
+
!canRead(access, owner))
|
|
56
|
+
return { outcome: "forbidden" };
|
|
57
|
+
const connection = await resolveConnection?.(i.scope.connection);
|
|
58
|
+
if (resolveConnection && (!connection || connection.provider !== i.provider))
|
|
59
|
+
return { outcome: "forbidden" };
|
|
60
|
+
if (catalog && catalog.operationsOf(i.provider).length === 0)
|
|
61
|
+
return { outcome: "forbidden" };
|
|
62
|
+
return safely(() => store.importBilling(i));
|
|
63
|
+
},
|
|
64
|
+
billingImports: async (access, q) => {
|
|
65
|
+
const invalid = validateBillingImportsQuery(q);
|
|
66
|
+
if (invalid)
|
|
67
|
+
return invalid;
|
|
68
|
+
if (!access.canReadBillingDetail ||
|
|
69
|
+
!canRead(access, { kind: "principal", ...q.scope }) ||
|
|
70
|
+
!(await canReadBudget(resolveOwnership, access, {
|
|
71
|
+
kind: "connection",
|
|
72
|
+
namespace: q.scope.namespace,
|
|
73
|
+
connection: q.connection,
|
|
74
|
+
})))
|
|
75
|
+
return { outcome: "forbidden" };
|
|
76
|
+
return read(() => store.billingImports(q));
|
|
77
|
+
},
|
|
33
78
|
expireReservations: (i) => command(i, () => store.expireReservations(i)),
|
|
34
79
|
reserve: async (requested) => {
|
|
80
|
+
if (requested.source === "import")
|
|
81
|
+
return {
|
|
82
|
+
outcome: "invalid",
|
|
83
|
+
field: "source",
|
|
84
|
+
reason: "import source is reserved for authorized billing evidence",
|
|
85
|
+
};
|
|
35
86
|
const early = validate({ ...requested, estimate: requested.estimate ?? [] }) ??
|
|
36
87
|
sourceValidation(requested) ??
|
|
37
88
|
provenanceValidation(requested);
|
|
@@ -96,10 +147,28 @@ export function createMeter({ store, policy = defaultPolicy, clock, resolveOwner
|
|
|
96
147
|
markDispatchIntent: (i) => command(i, () => store.markDispatchIntent(i)),
|
|
97
148
|
renewLease: (i) => command(i, () => store.renewLease(i)),
|
|
98
149
|
claimForRecovery: (i) => command(i, () => store.claimForRecovery(i)),
|
|
99
|
-
settle: (i) =>
|
|
100
|
-
|
|
150
|
+
settle: (i) => i.receipt.source === "import"
|
|
151
|
+
? Promise.resolve({
|
|
152
|
+
outcome: "invalid",
|
|
153
|
+
field: "receipt.source",
|
|
154
|
+
reason: "import evidence requires the atomic billing import command",
|
|
155
|
+
})
|
|
156
|
+
: command(i, () => store.settle(i)),
|
|
157
|
+
correct: (i) => i.receipt.source === "import"
|
|
158
|
+
? Promise.resolve({
|
|
159
|
+
outcome: "invalid",
|
|
160
|
+
field: "receipt.source",
|
|
161
|
+
reason: "import evidence requires the atomic billing import command",
|
|
162
|
+
})
|
|
163
|
+
: command(i, () => store.correct(i)),
|
|
101
164
|
releaseUndispatched: (i) => command(i, () => store.releaseUndispatched(i)),
|
|
102
165
|
countRequest: (i) => {
|
|
166
|
+
if (i.source === "import")
|
|
167
|
+
return Promise.resolve({
|
|
168
|
+
outcome: "invalid",
|
|
169
|
+
field: "source",
|
|
170
|
+
reason: "import source is reserved for authorized billing evidence",
|
|
171
|
+
});
|
|
103
172
|
const invalid = validate(i) ?? sourceValidation(i);
|
|
104
173
|
return invalid ? Promise.resolve(invalid) : safely(() => store.countRequest(i));
|
|
105
174
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@usagekit/meter",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"description": "Admission, reservations, settlement and recovery.",
|
|
@@ -23,8 +23,8 @@
|
|
|
23
23
|
"prepack": "node ../../scripts/package-license.mjs"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"@usagekit/core": "0.
|
|
27
|
-
"@usagekit/store": "0.
|
|
26
|
+
"@usagekit/core": "0.5.0",
|
|
27
|
+
"@usagekit/store": "0.5.0"
|
|
28
28
|
},
|
|
29
29
|
"sideEffects": false,
|
|
30
30
|
"publishConfig": {
|