mbase-sdk 0.0.4 → 0.0.5
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/README.md +82 -19
- package/dist/index.cjs +125 -19
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +113 -1
- package/dist/index.d.ts +113 -1
- package/dist/index.js +125 -20
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.d.cts
CHANGED
|
@@ -482,6 +482,51 @@ type WhoAmI = {
|
|
|
482
482
|
caller: string;
|
|
483
483
|
kind: "api_key" | "service";
|
|
484
484
|
};
|
|
485
|
+
/**
|
|
486
|
+
* A reserve is a check that means it: the same decision, and then a **hold**
|
|
487
|
+
* on the capacity until a track commits it, a release gives it back, or it
|
|
488
|
+
* expires. It is the gate for work that cannot be done twice.
|
|
489
|
+
*/
|
|
490
|
+
type ReserveParams = {
|
|
491
|
+
customer_id: string;
|
|
492
|
+
meter_id: string;
|
|
493
|
+
/** What the hold sets aside, `1 … 100000000000`. Defaults to `1`. */
|
|
494
|
+
quantity?: number;
|
|
495
|
+
/**
|
|
496
|
+
* The hold's id, and the event's id once a track commits it. A UUIDv7, and
|
|
497
|
+
* the SDK mints one when it is omitted.
|
|
498
|
+
*
|
|
499
|
+
* An id is for one reserve and its retries. Once its hold is committed or
|
|
500
|
+
* released the row is gone, so sending the same id again reserves afresh
|
|
501
|
+
* rather than finding anything.
|
|
502
|
+
*/
|
|
503
|
+
reservation_id?: string;
|
|
504
|
+
/**
|
|
505
|
+
* How long the hold counts against the pair before it comes back on its
|
|
506
|
+
* own: `1 … 900`, defaulting to `60`.
|
|
507
|
+
*
|
|
508
|
+
* It is a timeout on your own work, and the field callers get wrong. Too
|
|
509
|
+
* short and the hold evaporates mid-operation, which is worse than no hold
|
|
510
|
+
* at all because you believe you are protected; too long and a crash wedges
|
|
511
|
+
* the customer's capacity until it expires.
|
|
512
|
+
*/
|
|
513
|
+
expires_in_seconds?: number;
|
|
514
|
+
};
|
|
515
|
+
/** What the engine answers a reserve with, before the SDK adds its helpers. */
|
|
516
|
+
type ReserveResponse = CheckResult & {
|
|
517
|
+
/** Present only when allowed: a refusal holds nothing. */
|
|
518
|
+
reservation_id?: string;
|
|
519
|
+
quantity?: number;
|
|
520
|
+
expires_at?: string;
|
|
521
|
+
};
|
|
522
|
+
type ReleaseParams = {
|
|
523
|
+
reservation_id: string;
|
|
524
|
+
};
|
|
525
|
+
/** The hold that was given back, and what it had set aside. */
|
|
526
|
+
type ReleaseResult = {
|
|
527
|
+
reservation_id: string;
|
|
528
|
+
quantity: number;
|
|
529
|
+
};
|
|
485
530
|
|
|
486
531
|
/**
|
|
487
532
|
* Capacity handed to one customer on top of their plan. A grant with
|
|
@@ -714,6 +759,14 @@ declare class TooLateError extends InvalidRequestError {
|
|
|
714
759
|
readonly serverTime: Date | undefined;
|
|
715
760
|
constructor(args: ConstructorParameters<typeof APIError>[0]);
|
|
716
761
|
}
|
|
762
|
+
/**
|
|
763
|
+
* `422 reservation_mismatch`: the reservation id names a hold made for a
|
|
764
|
+
* different customer or meter than the call sends. A caller bug, refused
|
|
765
|
+
* before anything is written — nothing was recorded and the hold still
|
|
766
|
+
* stands.
|
|
767
|
+
*/
|
|
768
|
+
declare class ReservationMismatchError extends InvalidRequestError {
|
|
769
|
+
}
|
|
717
770
|
/**
|
|
718
771
|
* `422 cycle_change_requires_reset`: the two plans measure different cycles,
|
|
719
772
|
* so a `next_cycle` change has no shared boundary to wait for and a `prorate`
|
|
@@ -748,6 +801,43 @@ declare function errorFromResponse(args: {
|
|
|
748
801
|
body?: unknown;
|
|
749
802
|
}): APIError;
|
|
750
803
|
|
|
804
|
+
/**
|
|
805
|
+
* A granted hold, with the two calls that close it. `commit` is `track` under
|
|
806
|
+
* the hold's own id; `release` gives the capacity back.
|
|
807
|
+
*/
|
|
808
|
+
type Hold = ReserveResponse & {
|
|
809
|
+
allowed: true;
|
|
810
|
+
reservation_id: string;
|
|
811
|
+
quantity: number;
|
|
812
|
+
expires_at: string;
|
|
813
|
+
/**
|
|
814
|
+
* Records what the work actually cost and releases the rest. Omit the
|
|
815
|
+
* quantity to record what was held.
|
|
816
|
+
*
|
|
817
|
+
* Warns when it runs after `expires_at`: the hold stopped holding anything
|
|
818
|
+
* at that instant, so the work was no longer protected. That warning is the
|
|
819
|
+
* only feedback there is on an `expires_in_seconds` guessed too short.
|
|
820
|
+
*/
|
|
821
|
+
commit(quantity?: number, options?: RequestOptions): Promise<TrackResult>;
|
|
822
|
+
/**
|
|
823
|
+
* Gives the hold back. Nothing is recorded.
|
|
824
|
+
*
|
|
825
|
+
* Resolves with `null` rather than throwing when there is nothing left to
|
|
826
|
+
* release — already committed, already released. This is the one call meant
|
|
827
|
+
* to run on the failure path, where throwing would mask the error the
|
|
828
|
+
* caller is already handling, and "nothing left to release" is the outcome
|
|
829
|
+
* a release wanted anyway. `meterbase.release` throws the 404, as every
|
|
830
|
+
* other call does.
|
|
831
|
+
*/
|
|
832
|
+
release(options?: RequestOptions): Promise<ReleaseResult | null>;
|
|
833
|
+
};
|
|
834
|
+
/** A refused reserve holds nothing, so it carries no id and no expiry. */
|
|
835
|
+
type ReserveRefused = ReserveResponse & {
|
|
836
|
+
allowed: false;
|
|
837
|
+
reservation_id?: undefined;
|
|
838
|
+
expires_at?: undefined;
|
|
839
|
+
};
|
|
840
|
+
type ReserveResult = Hold | ReserveRefused;
|
|
751
841
|
declare class Meterbase {
|
|
752
842
|
#private;
|
|
753
843
|
readonly customers: Customers;
|
|
@@ -781,6 +871,28 @@ declare class Meterbase {
|
|
|
781
871
|
* which is how long a retry stays recognisable.
|
|
782
872
|
*/
|
|
783
873
|
track(params: TrackParams, options?: RequestOptions): Promise<TrackResult>;
|
|
874
|
+
/**
|
|
875
|
+
* Holds capacity for work that cannot be done twice, or refuses. The same
|
|
876
|
+
* decision `check` makes, and then a hold on the capacity until the work
|
|
877
|
+
* is committed, released, or expires.
|
|
878
|
+
*
|
|
879
|
+
* The rule for choosing: can you afford to do the work twice? `check`. No?
|
|
880
|
+
* `reserve`. A reserve costs a transaction where a check costs a cached
|
|
881
|
+
* read, refusals included, so it is not a per-request gate on cheap work.
|
|
882
|
+
*
|
|
883
|
+
* Two concurrent reserves for the same customer and meter cannot both be
|
|
884
|
+
* granted the same capacity; that is the whole point, and it is what
|
|
885
|
+
* `check` → work → `track` could never promise.
|
|
886
|
+
*/
|
|
887
|
+
reserve(params: ReserveParams, options?: RequestOptions): Promise<ReserveResult>;
|
|
888
|
+
/**
|
|
889
|
+
* Gives a hold back: the work did not happen, so nothing is recorded.
|
|
890
|
+
*
|
|
891
|
+
* Throws `NotFoundError` when the id names nothing — committed, released
|
|
892
|
+
* already, or never made, which are one absence with one meaning. Prefer
|
|
893
|
+
* `hold.release()`, which treats that as the success it is.
|
|
894
|
+
*/
|
|
895
|
+
release(params: ReleaseParams, options?: RequestOptions): Promise<ReleaseResult>;
|
|
784
896
|
/** Reports which workspace this key acts for. */
|
|
785
897
|
whoami(options?: RequestOptions): Promise<WhoAmI>;
|
|
786
898
|
}
|
|
@@ -797,4 +909,4 @@ type Meters = InstanceType<typeof Meters$1>;
|
|
|
797
909
|
type PlanAllowances = InstanceType<typeof PlanAllowances$1>;
|
|
798
910
|
type Plans = InstanceType<typeof Plans$1>;
|
|
799
911
|
|
|
800
|
-
export { APIError, type Allowance, type AllowanceGrantParams, type AllowanceListParams, type AllowanceSource, type ApplyMode, type AssignPlanParams, type Assignment, AuthenticationError, type CheckParams, type CheckRateLimit, type CheckReason, type CheckResult, ConflictError, ConnectionError, type Customer, type CustomerAllowances, type CustomerCreateParams, type CustomerListParams, type CustomerPlan, type CustomerUpdateParams, type CustomerWithPlan, type Customers, CycleChangeRequiresResetError, type EmbeddedPlan, InvalidIdempotencyKeyError, InvalidRequestError, type List, type Meter, type MeterCreateParams, type MeterListParams, type MeterUpdateParams, Meterbase, MeterbaseError, type MeterbaseOptions, type Meters, NotFoundError, PermissionDeniedError, type Plan, type PlanAllowance, type PlanAllowanceKind, type PlanAllowanceListParams, type PlanAllowanceSetParams, type PlanAllowances, type PlanChangeNowParams, type PlanChangeParams, type PlanCreateParams, type PlanCycle, type PlanListParams, type PlanMoveParams, type PlanUpdateParams, type Plans, RateLimitError, type Reconciliation, type RecordedReconciliation, type RequestOptions, ServerError, TimeoutError, TooLateError, type TrackParams, type TrackResult, type UsageEvent, type Validity, type WhoAmI, errorFromResponse };
|
|
912
|
+
export { APIError, type Allowance, type AllowanceGrantParams, type AllowanceListParams, type AllowanceSource, type ApplyMode, type AssignPlanParams, type Assignment, AuthenticationError, type CheckParams, type CheckRateLimit, type CheckReason, type CheckResult, ConflictError, ConnectionError, type Customer, type CustomerAllowances, type CustomerCreateParams, type CustomerListParams, type CustomerPlan, type CustomerUpdateParams, type CustomerWithPlan, type Customers, CycleChangeRequiresResetError, type EmbeddedPlan, type Hold, InvalidIdempotencyKeyError, InvalidRequestError, type List, type Meter, type MeterCreateParams, type MeterListParams, type MeterUpdateParams, Meterbase, MeterbaseError, type MeterbaseOptions, type Meters, NotFoundError, PermissionDeniedError, type Plan, type PlanAllowance, type PlanAllowanceKind, type PlanAllowanceListParams, type PlanAllowanceSetParams, type PlanAllowances, type PlanChangeNowParams, type PlanChangeParams, type PlanCreateParams, type PlanCycle, type PlanListParams, type PlanMoveParams, type PlanUpdateParams, type Plans, RateLimitError, type Reconciliation, type RecordedReconciliation, type ReleaseParams, type ReleaseResult, type RequestOptions, ReservationMismatchError, type ReserveParams, type ReserveRefused, type ReserveResponse, type ReserveResult, ServerError, TimeoutError, TooLateError, type TrackParams, type TrackResult, type UsageEvent, type Validity, type WhoAmI, errorFromResponse };
|
package/dist/index.d.ts
CHANGED
|
@@ -482,6 +482,51 @@ type WhoAmI = {
|
|
|
482
482
|
caller: string;
|
|
483
483
|
kind: "api_key" | "service";
|
|
484
484
|
};
|
|
485
|
+
/**
|
|
486
|
+
* A reserve is a check that means it: the same decision, and then a **hold**
|
|
487
|
+
* on the capacity until a track commits it, a release gives it back, or it
|
|
488
|
+
* expires. It is the gate for work that cannot be done twice.
|
|
489
|
+
*/
|
|
490
|
+
type ReserveParams = {
|
|
491
|
+
customer_id: string;
|
|
492
|
+
meter_id: string;
|
|
493
|
+
/** What the hold sets aside, `1 … 100000000000`. Defaults to `1`. */
|
|
494
|
+
quantity?: number;
|
|
495
|
+
/**
|
|
496
|
+
* The hold's id, and the event's id once a track commits it. A UUIDv7, and
|
|
497
|
+
* the SDK mints one when it is omitted.
|
|
498
|
+
*
|
|
499
|
+
* An id is for one reserve and its retries. Once its hold is committed or
|
|
500
|
+
* released the row is gone, so sending the same id again reserves afresh
|
|
501
|
+
* rather than finding anything.
|
|
502
|
+
*/
|
|
503
|
+
reservation_id?: string;
|
|
504
|
+
/**
|
|
505
|
+
* How long the hold counts against the pair before it comes back on its
|
|
506
|
+
* own: `1 … 900`, defaulting to `60`.
|
|
507
|
+
*
|
|
508
|
+
* It is a timeout on your own work, and the field callers get wrong. Too
|
|
509
|
+
* short and the hold evaporates mid-operation, which is worse than no hold
|
|
510
|
+
* at all because you believe you are protected; too long and a crash wedges
|
|
511
|
+
* the customer's capacity until it expires.
|
|
512
|
+
*/
|
|
513
|
+
expires_in_seconds?: number;
|
|
514
|
+
};
|
|
515
|
+
/** What the engine answers a reserve with, before the SDK adds its helpers. */
|
|
516
|
+
type ReserveResponse = CheckResult & {
|
|
517
|
+
/** Present only when allowed: a refusal holds nothing. */
|
|
518
|
+
reservation_id?: string;
|
|
519
|
+
quantity?: number;
|
|
520
|
+
expires_at?: string;
|
|
521
|
+
};
|
|
522
|
+
type ReleaseParams = {
|
|
523
|
+
reservation_id: string;
|
|
524
|
+
};
|
|
525
|
+
/** The hold that was given back, and what it had set aside. */
|
|
526
|
+
type ReleaseResult = {
|
|
527
|
+
reservation_id: string;
|
|
528
|
+
quantity: number;
|
|
529
|
+
};
|
|
485
530
|
|
|
486
531
|
/**
|
|
487
532
|
* Capacity handed to one customer on top of their plan. A grant with
|
|
@@ -714,6 +759,14 @@ declare class TooLateError extends InvalidRequestError {
|
|
|
714
759
|
readonly serverTime: Date | undefined;
|
|
715
760
|
constructor(args: ConstructorParameters<typeof APIError>[0]);
|
|
716
761
|
}
|
|
762
|
+
/**
|
|
763
|
+
* `422 reservation_mismatch`: the reservation id names a hold made for a
|
|
764
|
+
* different customer or meter than the call sends. A caller bug, refused
|
|
765
|
+
* before anything is written — nothing was recorded and the hold still
|
|
766
|
+
* stands.
|
|
767
|
+
*/
|
|
768
|
+
declare class ReservationMismatchError extends InvalidRequestError {
|
|
769
|
+
}
|
|
717
770
|
/**
|
|
718
771
|
* `422 cycle_change_requires_reset`: the two plans measure different cycles,
|
|
719
772
|
* so a `next_cycle` change has no shared boundary to wait for and a `prorate`
|
|
@@ -748,6 +801,43 @@ declare function errorFromResponse(args: {
|
|
|
748
801
|
body?: unknown;
|
|
749
802
|
}): APIError;
|
|
750
803
|
|
|
804
|
+
/**
|
|
805
|
+
* A granted hold, with the two calls that close it. `commit` is `track` under
|
|
806
|
+
* the hold's own id; `release` gives the capacity back.
|
|
807
|
+
*/
|
|
808
|
+
type Hold = ReserveResponse & {
|
|
809
|
+
allowed: true;
|
|
810
|
+
reservation_id: string;
|
|
811
|
+
quantity: number;
|
|
812
|
+
expires_at: string;
|
|
813
|
+
/**
|
|
814
|
+
* Records what the work actually cost and releases the rest. Omit the
|
|
815
|
+
* quantity to record what was held.
|
|
816
|
+
*
|
|
817
|
+
* Warns when it runs after `expires_at`: the hold stopped holding anything
|
|
818
|
+
* at that instant, so the work was no longer protected. That warning is the
|
|
819
|
+
* only feedback there is on an `expires_in_seconds` guessed too short.
|
|
820
|
+
*/
|
|
821
|
+
commit(quantity?: number, options?: RequestOptions): Promise<TrackResult>;
|
|
822
|
+
/**
|
|
823
|
+
* Gives the hold back. Nothing is recorded.
|
|
824
|
+
*
|
|
825
|
+
* Resolves with `null` rather than throwing when there is nothing left to
|
|
826
|
+
* release — already committed, already released. This is the one call meant
|
|
827
|
+
* to run on the failure path, where throwing would mask the error the
|
|
828
|
+
* caller is already handling, and "nothing left to release" is the outcome
|
|
829
|
+
* a release wanted anyway. `meterbase.release` throws the 404, as every
|
|
830
|
+
* other call does.
|
|
831
|
+
*/
|
|
832
|
+
release(options?: RequestOptions): Promise<ReleaseResult | null>;
|
|
833
|
+
};
|
|
834
|
+
/** A refused reserve holds nothing, so it carries no id and no expiry. */
|
|
835
|
+
type ReserveRefused = ReserveResponse & {
|
|
836
|
+
allowed: false;
|
|
837
|
+
reservation_id?: undefined;
|
|
838
|
+
expires_at?: undefined;
|
|
839
|
+
};
|
|
840
|
+
type ReserveResult = Hold | ReserveRefused;
|
|
751
841
|
declare class Meterbase {
|
|
752
842
|
#private;
|
|
753
843
|
readonly customers: Customers;
|
|
@@ -781,6 +871,28 @@ declare class Meterbase {
|
|
|
781
871
|
* which is how long a retry stays recognisable.
|
|
782
872
|
*/
|
|
783
873
|
track(params: TrackParams, options?: RequestOptions): Promise<TrackResult>;
|
|
874
|
+
/**
|
|
875
|
+
* Holds capacity for work that cannot be done twice, or refuses. The same
|
|
876
|
+
* decision `check` makes, and then a hold on the capacity until the work
|
|
877
|
+
* is committed, released, or expires.
|
|
878
|
+
*
|
|
879
|
+
* The rule for choosing: can you afford to do the work twice? `check`. No?
|
|
880
|
+
* `reserve`. A reserve costs a transaction where a check costs a cached
|
|
881
|
+
* read, refusals included, so it is not a per-request gate on cheap work.
|
|
882
|
+
*
|
|
883
|
+
* Two concurrent reserves for the same customer and meter cannot both be
|
|
884
|
+
* granted the same capacity; that is the whole point, and it is what
|
|
885
|
+
* `check` → work → `track` could never promise.
|
|
886
|
+
*/
|
|
887
|
+
reserve(params: ReserveParams, options?: RequestOptions): Promise<ReserveResult>;
|
|
888
|
+
/**
|
|
889
|
+
* Gives a hold back: the work did not happen, so nothing is recorded.
|
|
890
|
+
*
|
|
891
|
+
* Throws `NotFoundError` when the id names nothing — committed, released
|
|
892
|
+
* already, or never made, which are one absence with one meaning. Prefer
|
|
893
|
+
* `hold.release()`, which treats that as the success it is.
|
|
894
|
+
*/
|
|
895
|
+
release(params: ReleaseParams, options?: RequestOptions): Promise<ReleaseResult>;
|
|
784
896
|
/** Reports which workspace this key acts for. */
|
|
785
897
|
whoami(options?: RequestOptions): Promise<WhoAmI>;
|
|
786
898
|
}
|
|
@@ -797,4 +909,4 @@ type Meters = InstanceType<typeof Meters$1>;
|
|
|
797
909
|
type PlanAllowances = InstanceType<typeof PlanAllowances$1>;
|
|
798
910
|
type Plans = InstanceType<typeof Plans$1>;
|
|
799
911
|
|
|
800
|
-
export { APIError, type Allowance, type AllowanceGrantParams, type AllowanceListParams, type AllowanceSource, type ApplyMode, type AssignPlanParams, type Assignment, AuthenticationError, type CheckParams, type CheckRateLimit, type CheckReason, type CheckResult, ConflictError, ConnectionError, type Customer, type CustomerAllowances, type CustomerCreateParams, type CustomerListParams, type CustomerPlan, type CustomerUpdateParams, type CustomerWithPlan, type Customers, CycleChangeRequiresResetError, type EmbeddedPlan, InvalidIdempotencyKeyError, InvalidRequestError, type List, type Meter, type MeterCreateParams, type MeterListParams, type MeterUpdateParams, Meterbase, MeterbaseError, type MeterbaseOptions, type Meters, NotFoundError, PermissionDeniedError, type Plan, type PlanAllowance, type PlanAllowanceKind, type PlanAllowanceListParams, type PlanAllowanceSetParams, type PlanAllowances, type PlanChangeNowParams, type PlanChangeParams, type PlanCreateParams, type PlanCycle, type PlanListParams, type PlanMoveParams, type PlanUpdateParams, type Plans, RateLimitError, type Reconciliation, type RecordedReconciliation, type RequestOptions, ServerError, TimeoutError, TooLateError, type TrackParams, type TrackResult, type UsageEvent, type Validity, type WhoAmI, errorFromResponse };
|
|
912
|
+
export { APIError, type Allowance, type AllowanceGrantParams, type AllowanceListParams, type AllowanceSource, type ApplyMode, type AssignPlanParams, type Assignment, AuthenticationError, type CheckParams, type CheckRateLimit, type CheckReason, type CheckResult, ConflictError, ConnectionError, type Customer, type CustomerAllowances, type CustomerCreateParams, type CustomerListParams, type CustomerPlan, type CustomerUpdateParams, type CustomerWithPlan, type Customers, CycleChangeRequiresResetError, type EmbeddedPlan, type Hold, InvalidIdempotencyKeyError, InvalidRequestError, type List, type Meter, type MeterCreateParams, type MeterListParams, type MeterUpdateParams, Meterbase, MeterbaseError, type MeterbaseOptions, type Meters, NotFoundError, PermissionDeniedError, type Plan, type PlanAllowance, type PlanAllowanceKind, type PlanAllowanceListParams, type PlanAllowanceSetParams, type PlanAllowances, type PlanChangeNowParams, type PlanChangeParams, type PlanCreateParams, type PlanCycle, type PlanListParams, type PlanMoveParams, type PlanUpdateParams, type Plans, RateLimitError, type Reconciliation, type RecordedReconciliation, type ReleaseParams, type ReleaseResult, type RequestOptions, ReservationMismatchError, type ReserveParams, type ReserveRefused, type ReserveResponse, type ReserveResult, ServerError, TimeoutError, TooLateError, type TrackParams, type TrackResult, type UsageEvent, type Validity, type WhoAmI, errorFromResponse };
|
package/dist/index.js
CHANGED
|
@@ -39,6 +39,8 @@ var TooLateError = class extends InvalidRequestError {
|
|
|
39
39
|
this.serverTime = serverTime(args.body);
|
|
40
40
|
}
|
|
41
41
|
};
|
|
42
|
+
var ReservationMismatchError = class extends InvalidRequestError {
|
|
43
|
+
};
|
|
42
44
|
var CycleChangeRequiresResetError = class extends InvalidRequestError {
|
|
43
45
|
};
|
|
44
46
|
var RateLimitError = class extends APIError {
|
|
@@ -71,6 +73,8 @@ function errorFromResponse(args) {
|
|
|
71
73
|
return new CycleChangeRequiresResetError(args);
|
|
72
74
|
case "invalid_idempotency_key":
|
|
73
75
|
return new InvalidIdempotencyKeyError(args);
|
|
76
|
+
case "reservation_mismatch":
|
|
77
|
+
return new ReservationMismatchError(args);
|
|
74
78
|
case "too_late":
|
|
75
79
|
return new TooLateError(args);
|
|
76
80
|
default:
|
|
@@ -691,18 +695,83 @@ var Meterbase = class {
|
|
|
691
695
|
// `async` so that generating the key cannot throw synchronously out of a
|
|
692
696
|
// method that otherwise only ever rejects: one call, one way to fail.
|
|
693
697
|
async track(params, options) {
|
|
694
|
-
|
|
698
|
+
return this.#keyed(
|
|
699
|
+
IDEMPOTENCY_WINDOW_MS,
|
|
700
|
+
params.idempotency_key,
|
|
701
|
+
(key) => this.#client.request({
|
|
702
|
+
...options,
|
|
703
|
+
method: "POST",
|
|
704
|
+
path: "/v1/usage/track",
|
|
705
|
+
// Fixed before the retry loop is entered: every attempt of this call
|
|
706
|
+
// carries the same key, which is what makes replaying it safe.
|
|
707
|
+
body: { ...params, idempotency_key: key },
|
|
708
|
+
idempotent: true
|
|
709
|
+
})
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
/**
|
|
713
|
+
* Holds capacity for work that cannot be done twice, or refuses. The same
|
|
714
|
+
* decision `check` makes, and then a hold on the capacity until the work
|
|
715
|
+
* is committed, released, or expires.
|
|
716
|
+
*
|
|
717
|
+
* The rule for choosing: can you afford to do the work twice? `check`. No?
|
|
718
|
+
* `reserve`. A reserve costs a transaction where a check costs a cached
|
|
719
|
+
* read, refusals included, so it is not a per-request gate on cheap work.
|
|
720
|
+
*
|
|
721
|
+
* Two concurrent reserves for the same customer and meter cannot both be
|
|
722
|
+
* granted the same capacity; that is the whole point, and it is what
|
|
723
|
+
* `check` → work → `track` could never promise.
|
|
724
|
+
*/
|
|
725
|
+
async reserve(params, options) {
|
|
726
|
+
const response = await this.#keyed(
|
|
727
|
+
RESERVE_WINDOW_MS,
|
|
728
|
+
params.reservation_id,
|
|
729
|
+
(id) => this.#client.request({
|
|
730
|
+
...options,
|
|
731
|
+
method: "POST",
|
|
732
|
+
path: "/v1/usage/reserve",
|
|
733
|
+
body: { ...params, reservation_id: id },
|
|
734
|
+
// A retry under the same id finds the hold it already made rather
|
|
735
|
+
// than making a second one.
|
|
736
|
+
idempotent: true
|
|
737
|
+
})
|
|
738
|
+
);
|
|
739
|
+
return this.#hold(params, response);
|
|
740
|
+
}
|
|
741
|
+
/**
|
|
742
|
+
* Gives a hold back: the work did not happen, so nothing is recorded.
|
|
743
|
+
*
|
|
744
|
+
* Throws `NotFoundError` when the id names nothing — committed, released
|
|
745
|
+
* already, or never made, which are one absence with one meaning. Prefer
|
|
746
|
+
* `hold.release()`, which treats that as the success it is.
|
|
747
|
+
*/
|
|
748
|
+
release(params, options) {
|
|
749
|
+
return this.#client.request({
|
|
695
750
|
...options,
|
|
696
751
|
method: "POST",
|
|
697
|
-
path: "/v1/usage/
|
|
698
|
-
|
|
699
|
-
// carries the same key, which is what makes replaying it safe.
|
|
700
|
-
body: { ...params, idempotency_key: key2 },
|
|
701
|
-
idempotent: true
|
|
752
|
+
path: "/v1/usage/release",
|
|
753
|
+
body: params
|
|
702
754
|
});
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
755
|
+
}
|
|
756
|
+
/** Reports which workspace this key acts for. */
|
|
757
|
+
whoami(options) {
|
|
758
|
+
return this.#client.request({
|
|
759
|
+
...options,
|
|
760
|
+
method: "GET",
|
|
761
|
+
path: "/v1/whoami"
|
|
762
|
+
});
|
|
763
|
+
}
|
|
764
|
+
/**
|
|
765
|
+
* Sends a call that carries a caller-minted UUIDv7, minting one when the
|
|
766
|
+
* caller supplied none and re-minting once if this machine's clock put it
|
|
767
|
+
* outside the engine's window.
|
|
768
|
+
*
|
|
769
|
+
* A key the caller supplied is never re-minted: this cannot know whether it
|
|
770
|
+
* names work already recorded, and replacing it could count that work
|
|
771
|
+
* twice.
|
|
772
|
+
*/
|
|
773
|
+
async #keyed(window, supplied, send) {
|
|
774
|
+
if (supplied !== void 0) return send(supplied);
|
|
706
775
|
const key = idempotencyKey(this.#client.now());
|
|
707
776
|
const startedAt = Date.now();
|
|
708
777
|
try {
|
|
@@ -713,23 +782,59 @@ var Meterbase = class {
|
|
|
713
782
|
}
|
|
714
783
|
const elapsed = Date.now() - startedAt;
|
|
715
784
|
const serverAtStart = error.serverTime.getTime() - elapsed;
|
|
716
|
-
if (Math.abs(serverAtStart - mintedAtMs(key)) <=
|
|
717
|
-
throw error;
|
|
718
|
-
}
|
|
785
|
+
if (Math.abs(serverAtStart - mintedAtMs(key)) <= window) throw error;
|
|
719
786
|
this.#client.observeServerTime(error.serverTime);
|
|
720
787
|
return await send(idempotencyKey(this.#client.now()));
|
|
721
788
|
}
|
|
722
789
|
}
|
|
723
|
-
/**
|
|
724
|
-
|
|
725
|
-
return
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
790
|
+
/** Puts `commit` and `release` on a granted hold; a refusal is left as it is. */
|
|
791
|
+
#hold(params, response) {
|
|
792
|
+
if (!response.allowed) return response;
|
|
793
|
+
const id = response.reservation_id;
|
|
794
|
+
const expiresAt = response.expires_at;
|
|
795
|
+
if (id === void 0 || expiresAt === void 0) {
|
|
796
|
+
throw new MeterbaseError(
|
|
797
|
+
"The engine granted a reservation without an id or an expiry, so there is no hold to commit or release. The capacity it set aside comes back on its own."
|
|
798
|
+
);
|
|
799
|
+
}
|
|
800
|
+
const expiresAtMs = Date.parse(expiresAt);
|
|
801
|
+
return {
|
|
802
|
+
...response,
|
|
803
|
+
allowed: true,
|
|
804
|
+
reservation_id: id,
|
|
805
|
+
quantity: response.quantity ?? params.quantity ?? 1,
|
|
806
|
+
expires_at: expiresAt,
|
|
807
|
+
commit: (quantity, commitOptions) => {
|
|
808
|
+
if (Number.isFinite(expiresAtMs) && this.#client.now() > expiresAtMs && typeof globalThis.console?.warn === "function") {
|
|
809
|
+
globalThis.console.warn(
|
|
810
|
+
`Meterbase: committing reservation ${id} after it expired at ${expiresAt}. The event is still recorded, but the capacity was no longer held \u2014 raise expires_in_seconds for this work.`
|
|
811
|
+
);
|
|
812
|
+
}
|
|
813
|
+
return this.track(
|
|
814
|
+
{
|
|
815
|
+
customer_id: params.customer_id,
|
|
816
|
+
meter_id: params.meter_id,
|
|
817
|
+
quantity,
|
|
818
|
+
// The hold's own id, so the track commits it rather than
|
|
819
|
+
// recording a second event beside it.
|
|
820
|
+
idempotency_key: id
|
|
821
|
+
},
|
|
822
|
+
commitOptions
|
|
823
|
+
);
|
|
824
|
+
},
|
|
825
|
+
release: async (releaseOptions) => {
|
|
826
|
+
try {
|
|
827
|
+
return await this.release({ reservation_id: id }, releaseOptions);
|
|
828
|
+
} catch (error) {
|
|
829
|
+
if (error instanceof NotFoundError) return null;
|
|
830
|
+
throw error;
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
};
|
|
730
834
|
}
|
|
731
835
|
};
|
|
732
836
|
var IDEMPOTENCY_WINDOW_MS = 60 * 60 * 1e3;
|
|
837
|
+
var RESERVE_WINDOW_MS = 30 * 60 * 1e3;
|
|
733
838
|
function mintedAtMs(key) {
|
|
734
839
|
return Number.parseInt(key.replace(/-/g, "").slice(0, 12), 16);
|
|
735
840
|
}
|
|
@@ -762,6 +867,6 @@ function idempotencyKey(atMs) {
|
|
|
762
867
|
].join("-");
|
|
763
868
|
}
|
|
764
869
|
|
|
765
|
-
export { APIError, AuthenticationError, ConflictError, ConnectionError, CycleChangeRequiresResetError, InvalidIdempotencyKeyError, InvalidRequestError, Meterbase, MeterbaseError, NotFoundError, PermissionDeniedError, RateLimitError, ServerError, TimeoutError, TooLateError, errorFromResponse };
|
|
870
|
+
export { APIError, AuthenticationError, ConflictError, ConnectionError, CycleChangeRequiresResetError, InvalidIdempotencyKeyError, InvalidRequestError, Meterbase, MeterbaseError, NotFoundError, PermissionDeniedError, RateLimitError, ReservationMismatchError, ServerError, TimeoutError, TooLateError, errorFromResponse };
|
|
766
871
|
//# sourceMappingURL=index.js.map
|
|
767
872
|
//# sourceMappingURL=index.js.map
|