@schematichq/schematic-typescript-node 1.6.0 → 1.6.1

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 (25) hide show
  1. package/README.md +49 -28
  2. package/dist/api/resources/planmigrations/client/Client.d.ts +4 -2
  3. package/dist/api/resources/planmigrations/client/requests/CountMigrationsRequest.d.ts +3 -1
  4. package/dist/api/resources/planmigrations/client/requests/ListMigrationsRequest.d.ts +3 -1
  5. package/dist/api/resources/planmigrations/types/CountMigrationsParams.d.ts +1 -0
  6. package/dist/api/resources/planmigrations/types/ListMigrationsParams.d.ts +1 -0
  7. package/dist/api/resources/plans/client/Client.d.ts +4 -0
  8. package/dist/api/resources/plans/client/requests/CountPlansRequest.d.ts +6 -0
  9. package/dist/api/resources/plans/client/requests/ListPlansRequest.d.ts +6 -0
  10. package/dist/api/resources/plans/types/CountPlansParams.d.ts +4 -0
  11. package/dist/api/resources/plans/types/ListPlansParams.d.ts +4 -0
  12. package/dist/api/types/PlanVersionMigrationResponseData.d.ts +2 -0
  13. package/dist/credits/index.d.ts +2 -2
  14. package/dist/credits/server-check.d.ts +40 -0
  15. package/dist/credits/track.d.ts +1 -1
  16. package/dist/credits/types.d.ts +36 -4
  17. package/dist/index.d.ts +1 -1
  18. package/dist/index.js +9 -9
  19. package/dist/serialization/resources/planmigrations/types/CountMigrationsParams.d.ts +1 -0
  20. package/dist/serialization/resources/planmigrations/types/ListMigrationsParams.d.ts +1 -0
  21. package/dist/serialization/resources/plans/types/CountPlansParams.d.ts +2 -0
  22. package/dist/serialization/resources/plans/types/ListPlansParams.d.ts +2 -0
  23. package/dist/serialization/types/PlanVersionMigrationResponseData.d.ts +2 -0
  24. package/dist/wrapper.d.ts +41 -10
  25. package/package.json +5 -1
package/README.md CHANGED
@@ -800,13 +800,12 @@ const client = new SchematicClient({
800
800
 
801
801
  ## Credit Leases and Reservations
802
802
 
803
- For features metered by credit burndown (e.g. inference tokens), the SDK can enforce credit balances locally — without a Schematic API call on every check — using a lease-and-reservation system:
803
+ For features metered by credit burndown (e.g. inference tokens), `check()` holds credits for the work about to run and `trackWithReservation()` settles the hold with actual usage. The SDK gates in one of two modes:
804
804
 
805
- - A **lease** is a tranche of credits acquired transactionally from Schematic's API. It functions as a hold against the company's credit balance until it is consumed, released, or expires.
806
- - A **reservation** is a client-side hold carved out of the lease at check time, sized to the upper bound of the operation's usage. This protects against races and over-spend between the check and the eventual usage report.
807
- - When the work completes, a track call reports actual usage; the difference between reserved and actual usage is refunded to the lease.
805
+ - **Client mode** acquires a **lease**, a tranche of credits held against the company's balance, and carves a per-request **reservation** out of it locally, so a check needs no API call. It requires [DataStream](#datastream) (or [Replicator Mode](#replicator-mode)) and, across multiple processes, a shared Redis so every instance gates against the same lease.
806
+ - **Server mode** makes one `check-and-reserve` API call per check. No lease, no Redis, no local state.
808
807
 
809
- > **Requirements:** Credit leases require [DataStream](#datastream) (or [Replicator Mode](#replicator-mode)) — lease-bearing checks are evaluated locally against cached flag and company state. In a horizontally-scaled deployment, a shared Redis backend is also required so that all SDK instances gate against the same lease balance; without one, lease state is per-process.
808
+ `creditLeases.mode` defaults to `auto`: client when DataStream is enabled, server otherwise. Client mode suits high-throughput gating; server mode suits low-volume checks and operations that run for seconds.
810
809
 
811
810
  ### Setup
812
811
 
@@ -821,25 +820,35 @@ const client = new SchematicClient({
821
820
  apiKey: process.env.SCHEMATIC_API_KEY,
822
821
  useDataStream: true,
823
822
  dataStream: {
824
- redisClient, // also backs lease + reservation state automatically
823
+ redisClient, // also backs lease + reservation state
825
824
  },
826
825
  creditLeases: {
827
- defaultLeaseDuration: 5 * 60 * 1000, // lease lifetime (ms)
828
- defaultReservationTTL: 60 * 1000, // how long a reservation is held if not settled by a track (ms)
829
826
  defaultLeaseSize: 10_000, // credits requested per lease
830
- lowWaterMark: 0.25, // extend the lease in the background when its balance dips below this fraction
827
+ defaultLeaseDuration: 5 * 60 * 1000, // lease lifetime (ms)
828
+ defaultReservationTTL: 60 * 1000, // how long a reservation is held if no track settles it (ms)
831
829
  },
832
830
  });
833
831
  ```
834
832
 
835
- When `dataStream.redisClient` is configured, lease and reservation state automatically lives in the same Redis, with atomic reservations driven by Lua scripts — no additional configuration needed. To point lease state at a *different* Redis, set `creditLeases.redisClient` explicitly.
833
+ Set `creditLeases.redisClient` to keep lease state in a different Redis than the DataStream cache.
836
834
 
837
- ### Checking and tracking
835
+ Server mode needs only a TTL:
838
836
 
839
- `check()` reserves the operation's upper-bound usage against the lease and returns a reservation handle; `trackWithReservation()` settles it with actual usage:
837
+ ```ts
838
+ const client = new SchematicClient({
839
+ apiKey: process.env.SCHEMATIC_API_KEY,
840
+ creditLeases: {
841
+ defaultReservationTTL: 60 * 1000, // how long the server holds the credits if no track settles them (ms, max 1 hour)
842
+ },
843
+ });
844
+ ```
845
+
846
+ Only `mode` and `defaultReservationTTL` apply in server mode; the SDK warns at startup if a client-only option is set.
847
+
848
+ ### Checking and tracking
840
849
 
841
850
  ```ts
842
- // Per-request: reserve up to maxTokens against the lease.
851
+ // Reserve up to maxTokens for this operation.
843
852
  const result = await client.check(
844
853
  { company: { id: "your-company-id" } },
845
854
  "inference",
@@ -854,15 +863,25 @@ if (!result.allowed) {
854
863
 
855
864
  const inference = await runInference(/* ... */);
856
865
 
857
- // Report actual usage; the unused slice of the reservation is refunded to the lease.
858
- await client.trackWithReservation(result.reservation!, inference.tokensUsed);
866
+ // Report actual usage; the unused slice of the reservation is refunded.
867
+ if (result.reservation) {
868
+ await client.trackWithReservation(result.reservation, inference.tokensUsed);
869
+ } else {
870
+ await client.track({
871
+ event: "inference_tokens",
872
+ company: { id: "your-company-id" },
873
+ quantity: inference.tokensUsed,
874
+ });
875
+ }
859
876
  ```
860
877
 
861
- If the caller never settles a reservation, it expires after `defaultReservationTTL` and its credits are returned to the lease. If the work outlives the reservation's TTL, `trackWithReservation` still bills the usage — the track event carries a deterministic idempotency key, so duplicate or recovery emits never double-bill. However, the local lease balance is not re-debited on that late settle (the expired reservation's hold was already swept back to the lease), so it reads high until the lease rolls over. **Set `defaultReservationTTL` above the longest expected gap between `check()` and `trackWithReservation()`** to keep the local balance accurate.
878
+ A check can allow without taking a hold (the feature is not credit-metered, `usage` is 0, or the check failed open), and that usage still has to be tracked.
879
+
880
+ An unsettled reservation expires after `defaultReservationTTL` and its credits return to the lease. A late settle still bills the usage (the track event carries a deterministic idempotency key, so it never double-bills) but does not re-debit the local lease, so set `defaultReservationTTL` above the longest expected gap between `check()` and `trackWithReservation()`.
862
881
 
863
882
  ### Pre-warming
864
883
 
865
- To avoid paying the lease-acquire round trip on a session's first check, pre-warm leases when the user is identified:
884
+ Pre-warm leases when the user is identified, so a session's first check does not wait on a lease acquire:
866
885
 
867
886
  ```ts
868
887
  await client.identify(
@@ -874,11 +893,11 @@ await client.identify(
874
893
  );
875
894
  ```
876
895
 
877
- Or call `client.prewarm(evalCtx, creditTypeIds)` directly.
896
+ Or call `client.prewarm(evalCtx, creditTypeIds)` directly. Both are no-ops in server mode.
878
897
 
879
898
  ### Failure behavior
880
899
 
881
- By default, a check that cannot be gated (API unreachable, Redis down, lease exhausted) **fails closed** (`allowed: false`). Override per check for callers where letting traffic through is preferable to a denial:
900
+ A check that cannot be gated (API unreachable, Redis down, lease exhausted) fails closed by default. Override per check:
882
901
 
883
902
  ```ts
884
903
  const result = await client.check(evalCtx, "inference", {
@@ -888,20 +907,22 @@ const result = await client.check(evalCtx, "inference", {
888
907
  });
889
908
  ```
890
909
 
891
- `fail-open` does not skip evaluation: the flag's rules still run with the credit balance assumed sufficient, so plan targeting, overrides, and all non-credit conditions still apply — only the credit gate is bypassed.
910
+ In client mode, `fail-open` still evaluates the flag's rules with the credit balance assumed sufficient, so plan targeting and all non-credit conditions apply and only the credit gate is bypassed. In server mode it returns the flag's default value, which is `false` unless you pass `defaultValue` or configure a `flagDefaults` entry.
892
911
 
893
912
  ### Configuration options
894
913
 
895
914
  | Option | Type | Default | Description |
896
915
  |---|---|---|---|
897
- | `defaultLeaseDuration` | `number` | 5 minutes | Lease lifetime in milliseconds |
898
- | `defaultReservationTTL` | `number` | 60 seconds | How long an unsettled reservation is held (ms); set above your longest expected work duration |
899
- | `defaultLeaseSize` | `number` | 10000 | Credits requested per lease acquire/extend |
900
- | `lowWaterMark` | `number` | 0.25 | Extend in the background when the lease balance dips below this fraction |
901
- | `sweepIntervalMs` | `number` | 1000 | Sweep interval for expired reservations (ms) |
902
- | `redisClient` | `RedisClient` | `dataStream.redisClient` | Redis client for lease + reservation state |
903
- | `redisKeyPrefix` | `string` | `dataStream.redisKeyPrefix` | Key prefix for lease + reservation keys |
904
- | `overrides` | `object` | — | Per-credit-type overrides of the above (keyed by credit type ID) |
916
+ | `mode` | `"client" \| "server" \| "auto"` | `auto` | Where the credit hold lives; `auto` picks client when DataStream is enabled, server otherwise |
917
+ | `defaultReservationTTL` | `number` | 60 seconds | How long an unsettled reservation is held (ms) |
918
+ | `defaultLeaseDuration` | `number` | 5 minutes | (client mode) Lease lifetime (ms) |
919
+ | `defaultLeaseSize` | `number` | 10000 | (client mode) Credits requested per lease acquire or extend |
920
+ | `lowWaterMark` | `number` | 0.25 | (client mode) Extend in the background when the lease balance dips below this fraction |
921
+ | `sweepIntervalMs` | `number` | 1000 | (client mode) Sweep interval for expired reservations (ms) |
922
+ | `prewarmResolveTimeoutMs` | `number` | 5000 | (client mode) How long `prewarm()` waits for a freshly identified company to surface (ms) |
923
+ | `redisClient` | `RedisClient` | `dataStream.redisClient` | (client mode) Redis client for lease and reservation state |
924
+ | `redisKeyPrefix` | `string` | `dataStream.redisKeyPrefix` | (client mode) Key prefix for lease and reservation keys |
925
+ | `overrides` | `object` | — | (client mode) Per-credit-type overrides of the above, keyed by credit type ID |
905
926
 
906
927
  ## Testing
907
928
 
@@ -87,13 +87,14 @@ export declare class PlanmigrationsClient {
87
87
  *
88
88
  * @example
89
89
  * await client.planmigrations.listMigrations({
90
+ * featureId: "feature_id",
90
91
  * planVersionId: "plan_version_id",
91
92
  * status: "cancelled",
92
93
  * limit: 1000000,
93
94
  * offset: 1000000
94
95
  * })
95
96
  */
96
- listMigrations(request: Schematic.ListMigrationsRequest, requestOptions?: PlanmigrationsClient.RequestOptions): core.HttpResponsePromise<Schematic.ListMigrationsResponse>;
97
+ listMigrations(request?: Schematic.ListMigrationsRequest, requestOptions?: PlanmigrationsClient.RequestOptions): core.HttpResponsePromise<Schematic.ListMigrationsResponse>;
97
98
  private __listMigrations;
98
99
  /**
99
100
  * @param {Schematic.CreateMigrationInput} request
@@ -202,13 +203,14 @@ export declare class PlanmigrationsClient {
202
203
  *
203
204
  * @example
204
205
  * await client.planmigrations.countMigrations({
206
+ * featureId: "feature_id",
205
207
  * planVersionId: "plan_version_id",
206
208
  * status: "cancelled",
207
209
  * limit: 1000000,
208
210
  * offset: 1000000
209
211
  * })
210
212
  */
211
- countMigrations(request: Schematic.CountMigrationsRequest, requestOptions?: PlanmigrationsClient.RequestOptions): core.HttpResponsePromise<Schematic.CountMigrationsResponse>;
213
+ countMigrations(request?: Schematic.CountMigrationsRequest, requestOptions?: PlanmigrationsClient.RequestOptions): core.HttpResponsePromise<Schematic.CountMigrationsResponse>;
212
214
  private __countMigrations;
213
215
  /**
214
216
  * @param {Schematic.PreviewMigrationRequestBody} request
@@ -2,6 +2,7 @@ import type * as Schematic from "../../../../index";
2
2
  /**
3
3
  * @example
4
4
  * {
5
+ * featureId: "feature_id",
5
6
  * planVersionId: "plan_version_id",
6
7
  * status: "cancelled",
7
8
  * limit: 1000000,
@@ -9,7 +10,8 @@ import type * as Schematic from "../../../../index";
9
10
  * }
10
11
  */
11
12
  export interface CountMigrationsRequest {
12
- planVersionId: string;
13
+ featureId?: string;
14
+ planVersionId?: string;
13
15
  status?: Schematic.PlanVersionMigrationStatus;
14
16
  /** Page limit (default 100) */
15
17
  limit?: number;
@@ -2,6 +2,7 @@ import type * as Schematic from "../../../../index";
2
2
  /**
3
3
  * @example
4
4
  * {
5
+ * featureId: "feature_id",
5
6
  * planVersionId: "plan_version_id",
6
7
  * status: "cancelled",
7
8
  * limit: 1000000,
@@ -9,7 +10,8 @@ import type * as Schematic from "../../../../index";
9
10
  * }
10
11
  */
11
12
  export interface ListMigrationsRequest {
12
- planVersionId: string;
13
+ featureId?: string;
14
+ planVersionId?: string;
13
15
  status?: Schematic.PlanVersionMigrationStatus;
14
16
  /** Page limit (default 100) */
15
17
  limit?: number;
@@ -3,6 +3,7 @@ import type * as Schematic from "../../../index";
3
3
  * Input parameters
4
4
  */
5
5
  export interface CountMigrationsParams {
6
+ featureId?: string;
6
7
  /** Page limit (default 100) */
7
8
  limit?: number;
8
9
  /** Page offset (default 0) */
@@ -3,6 +3,7 @@ import type * as Schematic from "../../../index";
3
3
  * Input parameters
4
4
  */
5
5
  export interface ListMigrationsParams {
6
+ featureId?: string;
6
7
  /** Page limit (default 100) */
7
8
  limit?: number;
8
9
  /** Page offset (default 0) */
@@ -143,6 +143,8 @@ export declare class PlansClient {
143
143
  * q: "q",
144
144
  * scopedToCompanyId: "scoped_to_company_id",
145
145
  * withEntitlements: true,
146
+ * withPublishedVersion: true,
147
+ * withoutEntitlementForIncludeDrafts: true,
146
148
  * withoutEntitlementFor: "without_entitlement_for",
147
149
  * withoutPaidProductId: true,
148
150
  * limit: 1000000,
@@ -342,6 +344,8 @@ export declare class PlansClient {
342
344
  * q: "q",
343
345
  * scopedToCompanyId: "scoped_to_company_id",
344
346
  * withEntitlements: true,
347
+ * withPublishedVersion: true,
348
+ * withoutEntitlementForIncludeDrafts: true,
345
349
  * withoutEntitlementFor: "without_entitlement_for",
346
350
  * withoutPaidProductId: true,
347
351
  * limit: 1000000,
@@ -16,6 +16,8 @@ import type * as Schematic from "../../../../index";
16
16
  * q: "q",
17
17
  * scopedToCompanyId: "scoped_to_company_id",
18
18
  * withEntitlements: true,
19
+ * withPublishedVersion: true,
20
+ * withoutEntitlementForIncludeDrafts: true,
19
21
  * withoutEntitlementFor: "without_entitlement_for",
20
22
  * withoutPaidProductId: true,
21
23
  * limit: 1000000,
@@ -48,6 +50,10 @@ export interface CountPlansRequest {
48
50
  scopedToCompanyId?: string;
49
51
  /** Include each plan's entitlements in the response */
50
52
  withEntitlements?: boolean;
53
+ /** Only return plans that have a published version */
54
+ withPublishedVersion?: boolean;
55
+ /** With without_entitlement_for, also treat an entitlement on a plan's draft version as existing */
56
+ withoutEntitlementForIncludeDrafts?: boolean;
51
57
  /** Filter out plans that already have a plan entitlement for the specified feature ID */
52
58
  withoutEntitlementFor?: string;
53
59
  /** Filter out plans that have a paid billing product ID */
@@ -16,6 +16,8 @@ import type * as Schematic from "../../../../index";
16
16
  * q: "q",
17
17
  * scopedToCompanyId: "scoped_to_company_id",
18
18
  * withEntitlements: true,
19
+ * withPublishedVersion: true,
20
+ * withoutEntitlementForIncludeDrafts: true,
19
21
  * withoutEntitlementFor: "without_entitlement_for",
20
22
  * withoutPaidProductId: true,
21
23
  * limit: 1000000,
@@ -48,6 +50,10 @@ export interface ListPlansRequest {
48
50
  scopedToCompanyId?: string;
49
51
  /** Include each plan's entitlements in the response */
50
52
  withEntitlements?: boolean;
53
+ /** Only return plans that have a published version */
54
+ withPublishedVersion?: boolean;
55
+ /** With without_entitlement_for, also treat an entitlement on a plan's draft version as existing */
56
+ withoutEntitlementForIncludeDrafts?: boolean;
51
57
  /** Filter out plans that already have a plan entitlement for the specified feature ID */
52
58
  withoutEntitlementFor?: string;
53
59
  /** Filter out plans that have a paid billing product ID */
@@ -32,8 +32,12 @@ export interface CountPlansParams {
32
32
  scopedToCompanyId?: string;
33
33
  /** Include each plan's entitlements in the response */
34
34
  withEntitlements?: boolean;
35
+ /** Only return plans that have a published version */
36
+ withPublishedVersion?: boolean;
35
37
  /** Filter out plans that already have a plan entitlement for the specified feature ID */
36
38
  withoutEntitlementFor?: string;
39
+ /** With without_entitlement_for, also treat an entitlement on a plan's draft version as existing */
40
+ withoutEntitlementForIncludeDrafts?: boolean;
37
41
  /** Filter out plans that have a paid billing product ID */
38
42
  withoutPaidProductId?: boolean;
39
43
  }
@@ -32,8 +32,12 @@ export interface ListPlansParams {
32
32
  scopedToCompanyId?: string;
33
33
  /** Include each plan's entitlements in the response */
34
34
  withEntitlements?: boolean;
35
+ /** Only return plans that have a published version */
36
+ withPublishedVersion?: boolean;
35
37
  /** Filter out plans that already have a plan entitlement for the specified feature ID */
36
38
  withoutEntitlementFor?: string;
39
+ /** With without_entitlement_for, also treat an entitlement on a plan's draft version as existing */
40
+ withoutEntitlementForIncludeDrafts?: boolean;
37
41
  /** Filter out plans that have a paid billing product ID */
38
42
  withoutPaidProductId?: boolean;
39
43
  }
@@ -5,6 +5,8 @@ export interface PlanVersionMigrationResponseData {
5
5
  createdAt: Date;
6
6
  error?: string;
7
7
  failedCompanies: number;
8
+ featureId?: string;
9
+ featurePlanRolloutId?: string;
8
10
  id: string;
9
11
  nextDueAt?: Date;
10
12
  planId: string;
@@ -3,5 +3,5 @@ export { ReservationStore, type IReservationStore } from "./reservation-store";
3
3
  export { CreditLeaseManager } from "./lease-manager";
4
4
  export { RedisLeaseStore } from "./redis-lease-store";
5
5
  export { RedisReservationStore } from "./redis-reservation-store";
6
- export { DEFAULT_LEASE_DURATION_MS, DEFAULT_LEASE_SIZE, DEFAULT_LOW_WATER_MARK, DEFAULT_PREWARM_POLL_INTERVAL_MS, DEFAULT_PREWARM_RESOLVE_TIMEOUT_MS, DEFAULT_RESERVATION_TTL_MS, DEFAULT_SWEEP_INTERVAL_MS, } from "./types";
7
- export type { CreditLeaseConfig, ResolvedLeaseConfig, Reservation, CheckOptions, CheckResult, OnAcquireFailure, TrackWithReservationOptions, } from "./types";
6
+ export { DEFAULT_LEASE_DURATION_MS, DEFAULT_LEASE_SIZE, DEFAULT_LOW_WATER_MARK, DEFAULT_PREWARM_POLL_INTERVAL_MS, DEFAULT_PREWARM_RESOLVE_TIMEOUT_MS, DEFAULT_RESERVATION_TTL_MS, DEFAULT_SWEEP_INTERVAL_MS, MAX_RESERVATION_TTL_MS, RESERVATION_TTL_SKEW_ALLOWANCE_MS, } from "./types";
7
+ export type { CreditLeaseConfig, CreditLeaseMode, ResolvedLeaseConfig, Reservation, CheckOptions, CheckResult, OnAcquireFailure, TrackWithReservationOptions, } from "./types";
@@ -0,0 +1,40 @@
1
+ import type * as api from "../api";
2
+ import type { CreditsClient } from "../api/resources/credits/client/Client";
3
+ import type { FeaturesClient } from "../api/resources/features/client/Client";
4
+ import type { Logger } from "../logger";
5
+ import type { CheckOptions, CheckResult } from "./types";
6
+ /** Everything `checkWithServerReservation` needs to satisfy a server-mode check. */
7
+ export interface ServerCheckDeps {
8
+ features: FeaturesClient;
9
+ credits: CreditsClient;
10
+ logger: Logger;
11
+ /** How far out to set the hold's `expiresAt` (ms). */
12
+ reservationTTL: number;
13
+ /**
14
+ * Resolve the caller's default for this flag — `options.defaultValue` when
15
+ * set, otherwise the client-level flag default. Used by the fail-open
16
+ * branch, which has no local engine to re-run.
17
+ */
18
+ getDefault: () => boolean;
19
+ }
20
+ /**
21
+ * Drives a single `client.check` with `usage` set, in server mode.
22
+ *
23
+ * One `POST /flags/{key}/check-and-reserve` call does everything the client
24
+ * path spreads across a lease acquire, a local reserve, and a WASM eval: the
25
+ * server evaluates the flag against the company's real balance, applies the
26
+ * preflight cost, and takes the hold — all in the same round-trip. There is no
27
+ * lease, no local store, and no rules engine involved.
28
+ *
29
+ * The failure contract differs from client mode in one place. `fail-open` there
30
+ * means "re-run the engine with the credit balance assumed sufficient", so plan
31
+ * targeting and every non-credit condition still apply. Server mode has no
32
+ * local engine to re-run — the call that would have answered is the one that
33
+ * failed — so `fail-open` returns the caller's default value
34
+ * (`options.defaultValue`, else the client's configured flag default) instead.
35
+ * `fail-closed` denies, same as client mode.
36
+ *
37
+ * No `flag_check` event is enqueued here: the server logs the flag check for
38
+ * check-and-reserve itself, the same way the REST `checkFlag` path does.
39
+ */
40
+ export declare function checkWithServerReservation(deps: ServerCheckDeps, key: string, evalCtx: api.CheckFlagRequestBody, options: CheckOptions, fallback: () => Promise<CheckResult>): Promise<CheckResult>;
@@ -3,7 +3,7 @@ import type { IReservationStore } from "./reservation-store";
3
3
  import type { Reservation, TrackWithReservationOptions } from "./types";
4
4
  /** Outcome of consuming a reservation and building its Track event. */
5
5
  export interface ReservationConsumeResult {
6
- /** The Track event to emit. Always carries the `leaseId`. */
6
+ /** The Track event to emit. Always carries the `leaseId` (client mode) or the `reservationId` (server mode). */
7
7
  track: api.EventBodyTrack;
8
8
  /**
9
9
  * `true` when the reservation was still live and `consume` debited/refunded
@@ -13,23 +13,46 @@ import type { RedisClient } from "../cache/redis";
13
13
  export type OnAcquireFailure = "fail-open" | "fail-closed";
14
14
  export declare const DEFAULT_LEASE_DURATION_MS: number;
15
15
  export declare const DEFAULT_RESERVATION_TTL_MS: number;
16
+ export declare const MAX_RESERVATION_TTL_MS: number;
17
+ export declare const RESERVATION_TTL_SKEW_ALLOWANCE_MS: number;
16
18
  export declare const DEFAULT_LEASE_SIZE: number;
17
19
  export declare const DEFAULT_LOW_WATER_MARK: number;
18
20
  export declare const DEFAULT_SWEEP_INTERVAL_MS: number;
19
21
  export declare const DEFAULT_PREWARM_RESOLVE_TIMEOUT_MS: number;
20
22
  export declare const DEFAULT_PREWARM_POLL_INTERVAL_MS: number;
23
+ /**
24
+ * Where a credit hold lives for a `check()` that passes `usage`.
25
+ * - `client`: local leases over DataStream — the SDK acquires a tranche of
26
+ * credits up front and carves reservations out of it locally (today's
27
+ * behavior). Requires DataStream (or replicator mode), and a shared Redis
28
+ * backend to gate across pods.
29
+ * - `server`: one `check-and-reserve` API call per check — the server
30
+ * evaluates the flag and takes the hold in the same round-trip. No
31
+ * DataStream, no Redis, no local stores.
32
+ * - `auto` (default): `client` when DataStream is enabled, `server` otherwise.
33
+ */
34
+ export type CreditLeaseMode = "client" | "server" | "auto";
21
35
  /**
22
36
  * Configuration block enabling client-side lease + reservation behavior on
23
37
  * `client.check` / `client.trackWithReservation`. Omit to keep the SDK
24
38
  * lease-unaware (`check` falls back to a plain flag check).
25
39
  */
26
40
  export interface CreditLeaseConfig {
41
+ /**
42
+ * Where the credit hold lives. Default `auto` — client mode when
43
+ * DataStream is enabled, server mode otherwise. See `CreditLeaseMode`.
44
+ */
45
+ mode?: CreditLeaseMode;
27
46
  /** Default lease duration in milliseconds. Default `DEFAULT_LEASE_DURATION_MS` (5 minutes). */
28
47
  defaultLeaseDuration?: number;
29
48
  /**
30
49
  * Default reservation TTL in milliseconds. Default `DEFAULT_RESERVATION_TTL_MS`
31
- * (60 seconds). Size this above the longest expected gap between `check()`
32
- * and `trackWithReservation()`: a settle arriving after the TTL still bills
50
+ * (60 seconds). In server mode it is capped at `MAX_RESERVATION_TTL_MS`
51
+ * (1 hour) less `RESERVATION_TTL_SKEW_ALLOWANCE_MS`, since an hour out is
52
+ * the furthest the API will hold credits and it measures that against its
53
+ * own clock. Size this above the
54
+ * longest expected gap between `check()` and `trackWithReservation()`: a
55
+ * settle arriving after the TTL still bills
33
56
  * the server but doesn't re-debit the local lease (its hold was already
34
57
  * swept back), so the local balance reads high until the lease rolls over.
35
58
  */
@@ -71,7 +94,7 @@ export interface CreditLeaseConfig {
71
94
  */
72
95
  redisKeyPrefix?: string;
73
96
  /** Per-credit-type overrides (keyed by credit type ID). */
74
- overrides?: Record<string, Partial<Omit<CreditLeaseConfig, "overrides" | "sweepIntervalMs" | "redisClient" | "redisKeyPrefix">>>;
97
+ overrides?: Record<string, Partial<Omit<CreditLeaseConfig, "overrides" | "mode" | "sweepIntervalMs" | "redisClient" | "redisKeyPrefix">>>;
75
98
  }
76
99
  /** Resolved config for a single credit type after applying defaults + overrides. */
77
100
  export interface ResolvedLeaseConfig {
@@ -84,8 +107,17 @@ export interface ResolvedLeaseConfig {
84
107
  export interface Reservation {
85
108
  /** Opaque reservation ID. */
86
109
  id: string;
87
- /** Underlying lease ID this reservation draws from. */
110
+ /**
111
+ * Underlying lease ID this reservation draws from. In server mode there is
112
+ * no lease — this mirrors `id` so the field stays populated.
113
+ */
88
114
  leaseId: string;
115
+ /**
116
+ * Where the hold lives. `server` means the API holds the credits and the
117
+ * settling Track event routes by `reservation_id`; absent (or `client`)
118
+ * means the hold is a local carve-out of a lease.
119
+ */
120
+ mode?: "client" | "server";
89
121
  /** Company that owns the lease. */
90
122
  companyId: string;
91
123
  /** Credit type the reservation reserves against. */
package/dist/index.d.ts CHANGED
@@ -6,5 +6,5 @@ export { ConsoleLogger, LogLevel, type Logger } from "./logger";
6
6
  export { SchematicEnvironment } from "./environments";
7
7
  export { SchematicError, SchematicTimeoutError } from "./errors";
8
8
  export { RulesEngineClient } from "./rules-engine";
9
- export type { CheckOptions, CheckResult, CreditLeaseConfig, OnAcquireFailure, Reservation, TrackWithReservationOptions, } from "./credits";
9
+ export type { CheckOptions, CheckResult, CreditLeaseConfig, CreditLeaseMode, OnAcquireFailure, Reservation, TrackWithReservationOptions, } from "./credits";
10
10
  export { verifyWebhookSignature, verifySignature, computeSignature, computeHexSignature, WebhookSignatureError, WEBHOOK_SIGNATURE_HEADER, WEBHOOK_TIMESTAMP_HEADER } from "./webhooks";