@usagekit/meter 0.4.0 → 0.6.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.
Files changed (2) hide show
  1. package/dist/meter.js +71 -2
  2. package/package.json +12 -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) => command(i, () => store.settle(i)),
100
- correct: (i) => command(i, () => store.correct(i)),
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.4.0",
3
+ "version": "0.6.0",
4
4
  "type": "module",
5
5
  "license": "Apache-2.0",
6
6
  "description": "Admission, reservations, settlement and recovery.",
@@ -23,12 +23,21 @@
23
23
  "prepack": "node ../../scripts/package-license.mjs"
24
24
  },
25
25
  "dependencies": {
26
- "@usagekit/core": "0.4.0",
27
- "@usagekit/store": "0.4.0"
26
+ "@usagekit/core": "0.6.0",
27
+ "@usagekit/store": "0.6.0"
28
28
  },
29
29
  "sideEffects": false,
30
30
  "publishConfig": {
31
31
  "access": "restricted",
32
32
  "registry": "https://registry.npmjs.org/"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/msniezynski/usagekit.git",
37
+ "directory": "packages/meter"
38
+ },
39
+ "homepage": "https://usagekit.dev",
40
+ "bugs": {
41
+ "url": "https://github.com/msniezynski/usagekit/issues"
33
42
  }
34
43
  }