mbase-sdk 0.0.4 → 0.0.6

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/index.d.ts CHANGED
@@ -432,6 +432,70 @@ type PlanChangeParams = {
432
432
  type PlanChangeNowParams = Omit<PlanChangeParams, "effective">;
433
433
  /** Neither `effective` nor `reconciliation` is open: the call names both. */
434
434
  type PlanMoveParams = Pick<PlanChangeParams, "plan">;
435
+ /** `awaiting`: the plan meters this, but its only versions land later. */
436
+ type EntitlementState = "granted" | "awaiting";
437
+ /** `used` counts toward `amount`; `total` adds what grants paid for. */
438
+ type MeterEntitlement = {
439
+ meter_id: string;
440
+ state: EntitlementState;
441
+ version: PlanAllowance | null;
442
+ amount: number | null;
443
+ no_cap: boolean;
444
+ used: number;
445
+ total: number;
446
+ remaining: number | null;
447
+ reserved: number;
448
+ available: number | null;
449
+ incoming: {
450
+ version: PlanAllowance;
451
+ lands_at: string;
452
+ } | null;
453
+ };
454
+ /** The current period only, and only the meters the plan meters. */
455
+ type CustomerEntitlement = {
456
+ customer_id: string;
457
+ plan_id: string;
458
+ cycle: PlanCycle;
459
+ since: string;
460
+ period: {
461
+ start: string;
462
+ end: string;
463
+ };
464
+ meters: MeterEntitlement[];
465
+ };
466
+ type UsagePeriod = "current" | "last";
467
+ /** 1–90 UTC days, ending today. */
468
+ type UsageDaysParams = {
469
+ days: number;
470
+ period?: never;
471
+ };
472
+ type UsagePeriodParams = {
473
+ period: UsagePeriod;
474
+ days?: never;
475
+ };
476
+ type CustomerUsageParams = UsageDaysParams | UsagePeriodParams;
477
+ type DayUsage = {
478
+ /** A UTC day, `YYYY-MM-DD`. */
479
+ date: string;
480
+ quantity: number;
481
+ };
482
+ /** Every day of the range that has begun, zeros included. */
483
+ type MeterUsage = {
484
+ meter_id: string;
485
+ total: number;
486
+ days: DayUsage[];
487
+ };
488
+ /** `[from, to)`. Only the meters with usage in the range. */
489
+ type CustomerUsage = {
490
+ customer_id: string;
491
+ from: string;
492
+ to: string;
493
+ meters: MeterUsage[];
494
+ };
495
+ type MeterUsageHistory = MeterUsage & {
496
+ from: string;
497
+ to: string;
498
+ };
435
499
  /** `plan` is not accepted on a grant: the engine mints those itself when a
436
500
  * plan is assigned. */
437
501
  type AllowanceSource = "purchased" | "bonus" | "manual";
@@ -482,6 +546,51 @@ type WhoAmI = {
482
546
  caller: string;
483
547
  kind: "api_key" | "service";
484
548
  };
549
+ /**
550
+ * A reserve is a check that means it: the same decision, and then a **hold**
551
+ * on the capacity until a track commits it, a release gives it back, or it
552
+ * expires. It is the gate for work that cannot be done twice.
553
+ */
554
+ type ReserveParams = {
555
+ customer_id: string;
556
+ meter_id: string;
557
+ /** What the hold sets aside, `1 … 100000000000`. Defaults to `1`. */
558
+ quantity?: number;
559
+ /**
560
+ * The hold's id, and the event's id once a track commits it. A UUIDv7, and
561
+ * the SDK mints one when it is omitted.
562
+ *
563
+ * An id is for one reserve and its retries. Once its hold is committed or
564
+ * released the row is gone, so sending the same id again reserves afresh
565
+ * rather than finding anything.
566
+ */
567
+ reservation_id?: string;
568
+ /**
569
+ * How long the hold counts against the pair before it comes back on its
570
+ * own: `1 … 900`, defaulting to `60`.
571
+ *
572
+ * It is a timeout on your own work, and the field callers get wrong. Too
573
+ * short and the hold evaporates mid-operation, which is worse than no hold
574
+ * at all because you believe you are protected; too long and a crash wedges
575
+ * the customer's capacity until it expires.
576
+ */
577
+ expires_in_seconds?: number;
578
+ };
579
+ /** What the engine answers a reserve with, before the SDK adds its helpers. */
580
+ type ReserveResponse = CheckResult & {
581
+ /** Present only when allowed: a refusal holds nothing. */
582
+ reservation_id?: string;
583
+ quantity?: number;
584
+ expires_at?: string;
585
+ };
586
+ type ReleaseParams = {
587
+ reservation_id: string;
588
+ };
589
+ /** The hold that was given back, and what it had set aside. */
590
+ type ReleaseResult = {
591
+ reservation_id: string;
592
+ quantity: number;
593
+ };
485
594
 
486
595
  /**
487
596
  * Capacity handed to one customer on top of their plan. A grant with
@@ -620,6 +729,14 @@ declare class Customers$1 {
620
729
  * an empty collection rather than a 404.
621
730
  */
622
731
  retrieveByExternalId(externalId: string, options?: RequestOptions): Promise<CustomerWithPlan | null>;
732
+ /** `null` for a customer holding no plan, as `plan.retrieve` answers. */
733
+ entitlement(id: string, options?: RequestOptions): Promise<CustomerEntitlement | null>;
734
+ /**
735
+ * Usage per UTC day. A period the customer never had, holding no plan now
736
+ * or none before this period, is `null`.
737
+ */
738
+ usage(id: string, params: UsageDaysParams, options?: RequestOptions): Promise<CustomerUsage>;
739
+ usage(id: string, params: UsagePeriodParams, options?: RequestOptions): Promise<CustomerUsage | null>;
623
740
  update(id: string, params: CustomerUpdateParams, options?: RequestOptions): Promise<Customer>;
624
741
  /** Soft delete: usage and assignments keep referencing the customer. */
625
742
  delete(id: string, options?: RequestOptions): Promise<Customer>;
@@ -632,6 +749,8 @@ declare class Meters$1 {
632
749
  list(params?: MeterListParams, options?: RequestOptions): Promise<List<Meter>>;
633
750
  retrieve(id: string, options?: RequestOptions): Promise<Meter>;
634
751
  update(id: string, params: MeterUpdateParams, options?: RequestOptions): Promise<Meter>;
752
+ /** Every customer's usage of the meter per UTC day. */
753
+ usage(id: string, params: UsageDaysParams, options?: RequestOptions): Promise<MeterUsageHistory>;
635
754
  /** Soft delete: plans and usage keep referencing the meter. Idempotent. */
636
755
  archive(id: string, options?: RequestOptions): Promise<Meter>;
637
756
  }
@@ -714,6 +833,14 @@ declare class TooLateError extends InvalidRequestError {
714
833
  readonly serverTime: Date | undefined;
715
834
  constructor(args: ConstructorParameters<typeof APIError>[0]);
716
835
  }
836
+ /**
837
+ * `422 reservation_mismatch`: the reservation id names a hold made for a
838
+ * different customer or meter than the call sends. A caller bug, refused
839
+ * before anything is written — nothing was recorded and the hold still
840
+ * stands.
841
+ */
842
+ declare class ReservationMismatchError extends InvalidRequestError {
843
+ }
717
844
  /**
718
845
  * `422 cycle_change_requires_reset`: the two plans measure different cycles,
719
846
  * so a `next_cycle` change has no shared boundary to wait for and a `prorate`
@@ -748,6 +875,43 @@ declare function errorFromResponse(args: {
748
875
  body?: unknown;
749
876
  }): APIError;
750
877
 
878
+ /**
879
+ * A granted hold, with the two calls that close it. `commit` is `track` under
880
+ * the hold's own id; `release` gives the capacity back.
881
+ */
882
+ type Hold = ReserveResponse & {
883
+ allowed: true;
884
+ reservation_id: string;
885
+ quantity: number;
886
+ expires_at: string;
887
+ /**
888
+ * Records what the work actually cost and releases the rest. Omit the
889
+ * quantity to record what was held.
890
+ *
891
+ * Warns when it runs after `expires_at`: the hold stopped holding anything
892
+ * at that instant, so the work was no longer protected. That warning is the
893
+ * only feedback there is on an `expires_in_seconds` guessed too short.
894
+ */
895
+ commit(quantity?: number, options?: RequestOptions): Promise<TrackResult>;
896
+ /**
897
+ * Gives the hold back. Nothing is recorded.
898
+ *
899
+ * Resolves with `null` rather than throwing when there is nothing left to
900
+ * release — already committed, already released. This is the one call meant
901
+ * to run on the failure path, where throwing would mask the error the
902
+ * caller is already handling, and "nothing left to release" is the outcome
903
+ * a release wanted anyway. `meterbase.release` throws the 404, as every
904
+ * other call does.
905
+ */
906
+ release(options?: RequestOptions): Promise<ReleaseResult | null>;
907
+ };
908
+ /** A refused reserve holds nothing, so it carries no id and no expiry. */
909
+ type ReserveRefused = ReserveResponse & {
910
+ allowed: false;
911
+ reservation_id?: undefined;
912
+ expires_at?: undefined;
913
+ };
914
+ type ReserveResult = Hold | ReserveRefused;
751
915
  declare class Meterbase {
752
916
  #private;
753
917
  readonly customers: Customers;
@@ -781,6 +945,28 @@ declare class Meterbase {
781
945
  * which is how long a retry stays recognisable.
782
946
  */
783
947
  track(params: TrackParams, options?: RequestOptions): Promise<TrackResult>;
948
+ /**
949
+ * Holds capacity for work that cannot be done twice, or refuses. The same
950
+ * decision `check` makes, and then a hold on the capacity until the work
951
+ * is committed, released, or expires.
952
+ *
953
+ * The rule for choosing: can you afford to do the work twice? `check`. No?
954
+ * `reserve`. A reserve costs a transaction where a check costs a cached
955
+ * read, refusals included, so it is not a per-request gate on cheap work.
956
+ *
957
+ * Two concurrent reserves for the same customer and meter cannot both be
958
+ * granted the same capacity; that is the whole point, and it is what
959
+ * `check` → work → `track` could never promise.
960
+ */
961
+ reserve(params: ReserveParams, options?: RequestOptions): Promise<ReserveResult>;
962
+ /**
963
+ * Gives a hold back: the work did not happen, so nothing is recorded.
964
+ *
965
+ * Throws `NotFoundError` when the id names nothing — committed, released
966
+ * already, or never made, which are one absence with one meaning. Prefer
967
+ * `hold.release()`, which treats that as the success it is.
968
+ */
969
+ release(params: ReleaseParams, options?: RequestOptions): Promise<ReleaseResult>;
784
970
  /** Reports which workspace this key acts for. */
785
971
  whoami(options?: RequestOptions): Promise<WhoAmI>;
786
972
  }
@@ -797,4 +983,4 @@ type Meters = InstanceType<typeof Meters$1>;
797
983
  type PlanAllowances = InstanceType<typeof PlanAllowances$1>;
798
984
  type Plans = InstanceType<typeof Plans$1>;
799
985
 
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 };
986
+ 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 CustomerEntitlement, type CustomerListParams, type CustomerPlan, type CustomerUpdateParams, type CustomerUsage, type CustomerUsageParams, type CustomerWithPlan, type Customers, CycleChangeRequiresResetError, type DayUsage, type EmbeddedPlan, type EntitlementState, type Hold, InvalidIdempotencyKeyError, InvalidRequestError, type List, type Meter, type MeterCreateParams, type MeterEntitlement, type MeterListParams, type MeterUpdateParams, type MeterUsage, type MeterUsageHistory, 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 UsageDaysParams, type UsageEvent, type UsagePeriod, type UsagePeriodParams, 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:
@@ -131,7 +135,7 @@ var Client = class {
131
135
  this.#fetch = options.fetch ?? globalThis.fetch;
132
136
  if (typeof this.#fetch !== "function") {
133
137
  throw new MeterbaseError(
134
- "No fetch implementation: pass one as `fetch`, or run on Node 18+."
138
+ "No fetch implementation: pass one as `fetch`, or run on Node 20+."
135
139
  );
136
140
  }
137
141
  }
@@ -502,6 +506,36 @@ var Customers = class {
502
506
  });
503
507
  return data[0] ?? null;
504
508
  }
509
+ /** `null` for a customer holding no plan, as `plan.retrieve` answers. */
510
+ async entitlement(id, options) {
511
+ try {
512
+ return await this.#client.request({
513
+ ...options,
514
+ method: "GET",
515
+ path: `/v1/customers/${encodeURIComponent(id)}/entitlement`
516
+ });
517
+ } catch (error) {
518
+ if (error instanceof NotFoundError && error.code === "no_plan_assigned") {
519
+ return null;
520
+ }
521
+ throw error;
522
+ }
523
+ }
524
+ async usage(id, params, options) {
525
+ try {
526
+ return await this.#client.request({
527
+ ...options,
528
+ method: "GET",
529
+ path: `/v1/customers/${encodeURIComponent(id)}/usage`,
530
+ query: { days: params.days, period: params.period }
531
+ });
532
+ } catch (error) {
533
+ if (error instanceof NotFoundError && (error.code === "no_plan_assigned" || error.code === "no_previous_period")) {
534
+ return null;
535
+ }
536
+ throw error;
537
+ }
538
+ }
505
539
  update(id, params, options) {
506
540
  return this.#client.request({
507
541
  ...options,
@@ -557,6 +591,15 @@ var Meters = class {
557
591
  body: params
558
592
  });
559
593
  }
594
+ /** Every customer's usage of the meter per UTC day. */
595
+ usage(id, params, options) {
596
+ return this.#client.request({
597
+ ...options,
598
+ method: "GET",
599
+ path: `/v1/meters/${encodeURIComponent(id)}/usage`,
600
+ query: { days: params.days }
601
+ });
602
+ }
560
603
  /** Soft delete: plans and usage keep referencing the meter. Idempotent. */
561
604
  archive(id, options) {
562
605
  return this.#client.request({
@@ -691,18 +734,83 @@ var Meterbase = class {
691
734
  // `async` so that generating the key cannot throw synchronously out of a
692
735
  // method that otherwise only ever rejects: one call, one way to fail.
693
736
  async track(params, options) {
694
- const send = (key2) => this.#client.request({
737
+ return this.#keyed(
738
+ IDEMPOTENCY_WINDOW_MS,
739
+ params.idempotency_key,
740
+ (key) => this.#client.request({
741
+ ...options,
742
+ method: "POST",
743
+ path: "/v1/usage/track",
744
+ // Fixed before the retry loop is entered: every attempt of this call
745
+ // carries the same key, which is what makes replaying it safe.
746
+ body: { ...params, idempotency_key: key },
747
+ idempotent: true
748
+ })
749
+ );
750
+ }
751
+ /**
752
+ * Holds capacity for work that cannot be done twice, or refuses. The same
753
+ * decision `check` makes, and then a hold on the capacity until the work
754
+ * is committed, released, or expires.
755
+ *
756
+ * The rule for choosing: can you afford to do the work twice? `check`. No?
757
+ * `reserve`. A reserve costs a transaction where a check costs a cached
758
+ * read, refusals included, so it is not a per-request gate on cheap work.
759
+ *
760
+ * Two concurrent reserves for the same customer and meter cannot both be
761
+ * granted the same capacity; that is the whole point, and it is what
762
+ * `check` → work → `track` could never promise.
763
+ */
764
+ async reserve(params, options) {
765
+ const response = await this.#keyed(
766
+ RESERVE_WINDOW_MS,
767
+ params.reservation_id,
768
+ (id) => this.#client.request({
769
+ ...options,
770
+ method: "POST",
771
+ path: "/v1/usage/reserve",
772
+ body: { ...params, reservation_id: id },
773
+ // A retry under the same id finds the hold it already made rather
774
+ // than making a second one.
775
+ idempotent: true
776
+ })
777
+ );
778
+ return this.#hold(params, response);
779
+ }
780
+ /**
781
+ * Gives a hold back: the work did not happen, so nothing is recorded.
782
+ *
783
+ * Throws `NotFoundError` when the id names nothing — committed, released
784
+ * already, or never made, which are one absence with one meaning. Prefer
785
+ * `hold.release()`, which treats that as the success it is.
786
+ */
787
+ release(params, options) {
788
+ return this.#client.request({
695
789
  ...options,
696
790
  method: "POST",
697
- path: "/v1/usage/track",
698
- // Fixed before the retry loop is entered: every attempt of this call
699
- // carries the same key, which is what makes replaying it safe.
700
- body: { ...params, idempotency_key: key2 },
701
- idempotent: true
791
+ path: "/v1/usage/release",
792
+ body: params
702
793
  });
703
- if (params.idempotency_key !== void 0) {
704
- return send(params.idempotency_key);
705
- }
794
+ }
795
+ /** Reports which workspace this key acts for. */
796
+ whoami(options) {
797
+ return this.#client.request({
798
+ ...options,
799
+ method: "GET",
800
+ path: "/v1/whoami"
801
+ });
802
+ }
803
+ /**
804
+ * Sends a call that carries a caller-minted UUIDv7, minting one when the
805
+ * caller supplied none and re-minting once if this machine's clock put it
806
+ * outside the engine's window.
807
+ *
808
+ * A key the caller supplied is never re-minted: this cannot know whether it
809
+ * names work already recorded, and replacing it could count that work
810
+ * twice.
811
+ */
812
+ async #keyed(window, supplied, send) {
813
+ if (supplied !== void 0) return send(supplied);
706
814
  const key = idempotencyKey(this.#client.now());
707
815
  const startedAt = Date.now();
708
816
  try {
@@ -713,23 +821,59 @@ var Meterbase = class {
713
821
  }
714
822
  const elapsed = Date.now() - startedAt;
715
823
  const serverAtStart = error.serverTime.getTime() - elapsed;
716
- if (Math.abs(serverAtStart - mintedAtMs(key)) <= IDEMPOTENCY_WINDOW_MS) {
717
- throw error;
718
- }
824
+ if (Math.abs(serverAtStart - mintedAtMs(key)) <= window) throw error;
719
825
  this.#client.observeServerTime(error.serverTime);
720
826
  return await send(idempotencyKey(this.#client.now()));
721
827
  }
722
828
  }
723
- /** Reports which workspace this key acts for. */
724
- whoami(options) {
725
- return this.#client.request({
726
- ...options,
727
- method: "GET",
728
- path: "/v1/whoami"
729
- });
829
+ /** Puts `commit` and `release` on a granted hold; a refusal is left as it is. */
830
+ #hold(params, response) {
831
+ if (!response.allowed) return response;
832
+ const id = response.reservation_id;
833
+ const expiresAt = response.expires_at;
834
+ if (id === void 0 || expiresAt === void 0) {
835
+ throw new MeterbaseError(
836
+ "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."
837
+ );
838
+ }
839
+ const expiresAtMs = Date.parse(expiresAt);
840
+ return {
841
+ ...response,
842
+ allowed: true,
843
+ reservation_id: id,
844
+ quantity: response.quantity ?? params.quantity ?? 1,
845
+ expires_at: expiresAt,
846
+ commit: (quantity, commitOptions) => {
847
+ if (Number.isFinite(expiresAtMs) && this.#client.now() > expiresAtMs && typeof globalThis.console?.warn === "function") {
848
+ globalThis.console.warn(
849
+ `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.`
850
+ );
851
+ }
852
+ return this.track(
853
+ {
854
+ customer_id: params.customer_id,
855
+ meter_id: params.meter_id,
856
+ quantity,
857
+ // The hold's own id, so the track commits it rather than
858
+ // recording a second event beside it.
859
+ idempotency_key: id
860
+ },
861
+ commitOptions
862
+ );
863
+ },
864
+ release: async (releaseOptions) => {
865
+ try {
866
+ return await this.release({ reservation_id: id }, releaseOptions);
867
+ } catch (error) {
868
+ if (error instanceof NotFoundError) return null;
869
+ throw error;
870
+ }
871
+ }
872
+ };
730
873
  }
731
874
  };
732
875
  var IDEMPOTENCY_WINDOW_MS = 60 * 60 * 1e3;
876
+ var RESERVE_WINDOW_MS = 30 * 60 * 1e3;
733
877
  function mintedAtMs(key) {
734
878
  return Number.parseInt(key.replace(/-/g, "").slice(0, 12), 16);
735
879
  }
@@ -762,6 +906,6 @@ function idempotencyKey(atMs) {
762
906
  ].join("-");
763
907
  }
764
908
 
765
- export { APIError, AuthenticationError, ConflictError, ConnectionError, CycleChangeRequiresResetError, InvalidIdempotencyKeyError, InvalidRequestError, Meterbase, MeterbaseError, NotFoundError, PermissionDeniedError, RateLimitError, ServerError, TimeoutError, TooLateError, errorFromResponse };
909
+ export { APIError, AuthenticationError, ConflictError, ConnectionError, CycleChangeRequiresResetError, InvalidIdempotencyKeyError, InvalidRequestError, Meterbase, MeterbaseError, NotFoundError, PermissionDeniedError, RateLimitError, ReservationMismatchError, ServerError, TimeoutError, TooLateError, errorFromResponse };
766
910
  //# sourceMappingURL=index.js.map
767
911
  //# sourceMappingURL=index.js.map