@onesub/server 0.19.0 → 0.21.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 (39) hide show
  1. package/README.md +1 -0
  2. package/dist/__tests__/apple-jws-cache.test.d.ts +16 -0
  3. package/dist/__tests__/apple-jws-cache.test.d.ts.map +1 -0
  4. package/dist/__tests__/metrics-aggregate.test.d.ts +10 -0
  5. package/dist/__tests__/metrics-aggregate.test.d.ts.map +1 -0
  6. package/dist/__tests__/metrics-cache.test.d.ts +11 -0
  7. package/dist/__tests__/metrics-cache.test.d.ts.map +1 -0
  8. package/dist/__tests__/purchase-product-lookup.test.d.ts +17 -0
  9. package/dist/__tests__/purchase-product-lookup.test.d.ts.map +1 -0
  10. package/dist/__tests__/verified-key-cache.test.d.ts +18 -0
  11. package/dist/__tests__/verified-key-cache.test.d.ts.map +1 -0
  12. package/dist/http.d.ts.map +1 -1
  13. package/dist/index.cjs +341 -199
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.d.ts +1 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +342 -201
  18. package/dist/index.js.map +1 -1
  19. package/dist/metrics-aggregate.d.ts +82 -0
  20. package/dist/metrics-aggregate.d.ts.map +1 -0
  21. package/dist/metrics-cache.d.ts +20 -0
  22. package/dist/metrics-cache.d.ts.map +1 -0
  23. package/dist/providers/apple.d.ts +2 -0
  24. package/dist/providers/apple.d.ts.map +1 -1
  25. package/dist/providers/verified-key-cache.d.ts +75 -0
  26. package/dist/providers/verified-key-cache.d.ts.map +1 -0
  27. package/dist/routes/admin.d.ts.map +1 -1
  28. package/dist/routes/entitlements.d.ts +18 -1
  29. package/dist/routes/entitlements.d.ts.map +1 -1
  30. package/dist/routes/metrics.d.ts +8 -3
  31. package/dist/routes/metrics.d.ts.map +1 -1
  32. package/dist/routes/purchase.d.ts.map +1 -1
  33. package/dist/store.d.ts +25 -2
  34. package/dist/store.d.ts.map +1 -1
  35. package/dist/stores/postgres.d.ts +10 -0
  36. package/dist/stores/postgres.d.ts.map +1 -1
  37. package/dist/stores/redis.d.ts +12 -0
  38. package/dist/stores/redis.d.ts.map +1 -1
  39. package/package.json +2 -2
@@ -0,0 +1,82 @@
1
+ import type { MetricsActiveResponse, MetricsBucket, MetricsGroupBy, PurchaseInfo, SubscriptionInfo } from '@onesub/shared';
2
+ /**
3
+ * In-memory reduction of store records into the metrics responses.
4
+ *
5
+ * Split out of the route handlers for two reasons. It was duplicated four times
6
+ * — each range endpoint re-implemented the same window filter, the same
7
+ * product/platform tallies, and the same zero-filled daily bucketing — and none
8
+ * of it was reachable by a test without standing up an HTTP server. Both
9
+ * problems go away when the reduction is a pure function of the records.
10
+ *
11
+ * This is deliberately store-agnostic. Aggregating in the application process
12
+ * still costs one full read per query, which is the remaining scaling limit for
13
+ * large deployments; pushing the aggregation down into SQL requires per-store
14
+ * support and is separate work. What lives here is the semantics every store
15
+ * must agree on, so a pushdown implementation has something to be tested
16
+ * against.
17
+ */
18
+ /** Minimum shape the tallies need. Both `SubscriptionInfo` and `PurchaseInfo` satisfy it. */
19
+ interface Countable {
20
+ productId: string;
21
+ platform: string;
22
+ }
23
+ /** The count-shaped part of a metrics response; the route adds `from`/`to`. */
24
+ export interface RangeAggregate {
25
+ total: number;
26
+ byProduct: Record<string, number>;
27
+ byPlatform: Record<string, number>;
28
+ /** Present only when `groupBy: 'day'` was requested. */
29
+ buckets?: MetricsBucket[];
30
+ }
31
+ export interface RangeAggregateOptions<T> {
32
+ /** Inclusive window bounds, ms-since-epoch. */
33
+ fromMs: number;
34
+ toMs: number;
35
+ groupBy: MetricsGroupBy;
36
+ /**
37
+ * ISO timestamp that places a record in the window and its daily bucket —
38
+ * `purchasedAt` for "started", `expiresAt` for "expired".
39
+ */
40
+ anchor: (record: T) => string;
41
+ /** Records this rejects are ignored entirely (status / type gates). */
42
+ include?: (record: T) => boolean;
43
+ }
44
+ /**
45
+ * UTC `YYYY-MM-DD` for an epoch-ms instant.
46
+ *
47
+ * UTC keeps bucket assignment deterministic regardless of server timezone:
48
+ * Postgres `updated_at` is UTC and `SubscriptionInfo` dates carry explicit zone
49
+ * offsets, so a local-time key would silently shift every boundary when the
50
+ * process moved between regions.
51
+ */
52
+ export declare function utcDateKey(ms: number): string;
53
+ /**
54
+ * Zero-filled daily series across `[fromMs, toMs]`, inclusive of both boundary
55
+ * days. Zero-filling is what lets a chart render a flat stretch as flat rather
56
+ * than as a gap.
57
+ */
58
+ export declare function emptyDailyBuckets(fromMs: number, toMs: number): MetricsBucket[];
59
+ /**
60
+ * Tally records whose anchor timestamp falls inside the window.
61
+ *
62
+ * Records outside the window, and records rejected by `include`, contribute
63
+ * nothing — not to `total`, not to the distributions, not to a bucket.
64
+ */
65
+ export declare function aggregateRange<T extends Countable>(records: readonly T[], opts: RangeAggregateOptions<T>): RangeAggregate;
66
+ /** A subscription counts as currently entitled: allowed status AND not yet expired. */
67
+ export declare function isActiveSubscription(sub: SubscriptionInfo, nowMs: number): boolean;
68
+ /** A subscription counts as ended: the store recorded it expired or canceled. */
69
+ export declare function isEndedSubscription(sub: SubscriptionInfo): boolean;
70
+ /** Non-consumables grant an ongoing right; consumables are a spent resource. */
71
+ export declare function isNonConsumable(purchase: PurchaseInfo): boolean;
72
+ /**
73
+ * Point-in-time entitlement snapshot.
74
+ *
75
+ * `byProduct` is subscriptions-only and `byProductPurchases` is
76
+ * non-consumables-only, so a dashboard can show "subscription mix" and
77
+ * "lifetime mix" as separate panels. `byPlatform` deliberately spans both —
78
+ * it answers "where are my paying users", which is not a per-kind question.
79
+ */
80
+ export declare function aggregateActive(subs: readonly SubscriptionInfo[], purchases: readonly PurchaseInfo[], nowMs: number): MetricsActiveResponse;
81
+ export {};
82
+ //# sourceMappingURL=metrics-aggregate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics-aggregate.d.ts","sourceRoot":"","sources":["../src/metrics-aggregate.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,qBAAqB,EACrB,aAAa,EACb,cAAc,EACd,YAAY,EACZ,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAGxB;;;;;;;;;;;;;;;GAeG;AAEH,6FAA6F;AAC7F,UAAU,SAAS;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,+EAA+E;AAC/E,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,UAAU,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACnC,wDAAwD;IACxD,OAAO,CAAC,EAAE,aAAa,EAAE,CAAC;CAC3B;AAED,MAAM,WAAW,qBAAqB,CAAC,CAAC;IACtC,+CAA+C;IAC/C,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,cAAc,CAAC;IACxB;;;OAGG;IACH,MAAM,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,MAAM,CAAC;IAC9B,uEAAuE;IACvE,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,OAAO,CAAC;CAClC;AAQD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAM7C;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,aAAa,EAAE,CAW/E;AAED;;;;;GAKG;AACH,wBAAgB,cAAc,CAAC,CAAC,SAAS,SAAS,EAChD,OAAO,EAAE,SAAS,CAAC,EAAE,EACrB,IAAI,EAAE,qBAAqB,CAAC,CAAC,CAAC,GAC7B,cAAc,CA0BhB;AAED,uFAAuF;AACvF,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,gBAAgB,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAIlF;AAED,iFAAiF;AACjF,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAIlE;AAED,gFAAgF;AAChF,wBAAgB,eAAe,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAE/D;AAED;;;;;;;GAOG;AACH,wBAAgB,eAAe,CAC7B,IAAI,EAAE,SAAS,gBAAgB,EAAE,EACjC,SAAS,EAAE,SAAS,YAAY,EAAE,EAClC,KAAK,EAAE,MAAM,GACZ,qBAAqB,CAgCvB"}
@@ -0,0 +1,20 @@
1
+ import type { CacheAdapter } from './cache.js';
2
+ export interface MetricsCache {
3
+ /**
4
+ * Return the value for `keyParts`, computing it via `produce` on a miss.
5
+ *
6
+ * `keyParts` must not contain a raw request timestamp — see
7
+ * `quantizeToWindow`.
8
+ */
9
+ resolve<T>(keyParts: readonly (string | number)[], produce: () => Promise<T>): Promise<T>;
10
+ /** Snap a millisecond timestamp onto the TTL grid, for use in a cache key. */
11
+ quantizeToWindow(ms: number): number;
12
+ }
13
+ /**
14
+ * @param ttlSeconds Freshness bound. `0` (or less) disables the cache.
15
+ * @param storage Backing store. Defaults to storage private to this
16
+ * instance — see the note above on why this is not the shared
17
+ * adapter. Injectable for tests.
18
+ */
19
+ export declare function createMetricsCache(ttlSeconds: number, storage?: CacheAdapter): MetricsCache;
20
+ //# sourceMappingURL=metrics-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metrics-cache.d.ts","sourceRoot":"","sources":["../src/metrics-cache.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAkC/C,MAAM,WAAW,YAAY;IAC3B;;;;;OAKG;IACH,OAAO,CAAC,CAAC,EAAE,QAAQ,EAAE,SAAS,CAAC,MAAM,GAAG,MAAM,CAAC,EAAE,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC;IAC1F,8EAA8E;IAC9E,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAAC;CACtC;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAChC,UAAU,EAAE,MAAM,EAClB,OAAO,GAAE,YAAyC,GACjD,YAAY,CAgDd"}
@@ -20,6 +20,8 @@ type AppleConfig = NonNullable<OneSubServerConfig['apple']>;
20
20
  * Exported for unit testing.
21
21
  */
22
22
  export declare function assertIssuerCanSign(cert: Pick<X509Certificate, 'ca'>, index: number): void;
23
+ /** Test/diagnostic hook — drop every memoised chain verification. */
24
+ export declare function clearAppleCertCache(): void;
23
25
  /**
24
26
  * Decode and verify a StoreKit 2 signed transaction JWS.
25
27
  *
@@ -1 +1 @@
1
- {"version":3,"file":"apple.d.ts","sourceRoot":"","sources":["../../src/providers/apple.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"apple.d.ts","sourceRoot":"","sources":["../../src/providers/apple.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Memo for "this certificate chain was verified, and here is the key it
3
+ * authorises" — the result of an expensive, purely local verification.
4
+ *
5
+ * Why this exists: Apple's StoreKit JWS verification re-parses the x5c chain,
6
+ * re-checks every issuer signature against a bundled Apple root, and re-imports
7
+ * the leaf public key on EVERY receipt and EVERY webhook. All of it is
8
+ * synchronous CPU work on the event loop, and Apple's leaf certificates are
9
+ * stable for weeks — so the same chain is verified over and over with the same
10
+ * outcome.
11
+ *
12
+ * Why this is NOT the pluggable `CacheAdapter`: that adapter exists to share
13
+ * *rate-limited network* results (Apple API JWTs, Google OAuth tokens) between
14
+ * cluster nodes, and it serialises values as JSON. An imported key is not
15
+ * JSON-serialisable — a Redis-backed adapter would silently round-trip it to
16
+ * `{}` — and what is being avoided here is local CPU, not a network call or a
17
+ * quota. A Redis round-trip would plausibly cost more than the verification it
18
+ * replaces. So this cache is deliberately process-local.
19
+ *
20
+ * Security invariants, all enforced below and covered by tests:
21
+ *
22
+ * - The entry key covers EVERY input to the verification, including the
23
+ * algorithm the key was imported for. A different chain, or the same chain
24
+ * claiming a different `alg`, is a different entry — it can never reuse a
25
+ * key that was authorised for something else.
26
+ * - A failed verification is never stored. A rejected `produce` propagates and
27
+ * leaves the cache untouched, so a bad chain is re-verified (and re-rejected)
28
+ * every time rather than being remembered either way.
29
+ * - No entry outlives the chain that produced it. `notAfter` (the earliest
30
+ * expiry in the verified chain) is a hard ceiling on the entry's lifetime,
31
+ * so an expired certificate can never be served from cache.
32
+ */
33
+ /** What a successful verification yields: the key, and when it stops being valid. */
34
+ export interface VerifiedKey<K> {
35
+ key: K;
36
+ /**
37
+ * Earliest `notAfter` across the verified chain, as ms-since-epoch. The cache
38
+ * entry is dropped at or before this instant, never after it.
39
+ */
40
+ notAfter: number;
41
+ }
42
+ export interface VerifiedKeyCacheOptions {
43
+ /**
44
+ * Upper bound on entries. Only successfully verified chains are stored, so in
45
+ * practice this holds a handful (Apple production + sandbox leaves, across a
46
+ * rotation). The cap is defence in depth against unbounded growth.
47
+ */
48
+ maxEntries?: number;
49
+ /** Upper bound on an entry's lifetime, independent of certificate expiry. */
50
+ maxTtlMs?: number;
51
+ /** Injectable clock. Tests drive expiry with it; production uses `Date.now`. */
52
+ now?: () => number;
53
+ }
54
+ export declare class VerifiedKeyCache<K> {
55
+ private readonly entries;
56
+ private readonly maxEntries;
57
+ private readonly maxTtlMs;
58
+ private readonly now;
59
+ constructor(opts?: VerifiedKeyCacheOptions);
60
+ /**
61
+ * Return the key for `parts`, verifying via `produce` only on a miss.
62
+ *
63
+ * `parts` must contain every input the verification depends on — for Apple
64
+ * that is the JWS `alg` plus the full x5c chain. Anything left out would let
65
+ * one input's result be served for a different input.
66
+ */
67
+ resolve(parts: readonly string[], produce: () => Promise<VerifiedKey<K>>): Promise<K>;
68
+ /** Entries currently held, including any not yet pruned. Test/diagnostic use. */
69
+ get size(): number;
70
+ /** Drop everything. Test/diagnostic use. */
71
+ clear(): void;
72
+ /** Make room: expired entries first, then the oldest insertions. */
73
+ private prune;
74
+ }
75
+ //# sourceMappingURL=verified-key-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verified-key-cache.d.ts","sourceRoot":"","sources":["../../src/providers/verified-key-cache.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AAEH,qFAAqF;AACrF,MAAM,WAAW,WAAW,CAAC,CAAC;IAC5B,GAAG,EAAE,CAAC,CAAC;IACP;;;OAGG;IACH,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,uBAAuB;IACtC;;;;OAIG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,6EAA6E;IAC7E,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAWD,qBAAa,gBAAgB,CAAC,CAAC;IAC7B,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA+B;IACvD,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;IACpC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;gBAEvB,IAAI,GAAE,uBAA4B;IAM9C;;;;;;OAMG;IACG,OAAO,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IA0B3F,iFAAiF;IACjF,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED,4CAA4C;IAC5C,KAAK,IAAI,IAAI;IAIb,oEAAoE;IACpE,OAAO,CAAC,KAAK;CAWd"}
@@ -1 +1 @@
1
- {"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../../src/routes/admin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGjC,OAAO,KAAK,EAGV,kBAAkB,EAKnB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAQxD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,kBAAkB,EAC1B,aAAa,EAAE,aAAa,EAC5B,KAAK,EAAE,iBAAiB,EACxB,YAAY,CAAC,EAAE,YAAY,GAC1B,MAAM,GAAG,IAAI,CA8Vf"}
1
+ {"version":3,"file":"admin.d.ts","sourceRoot":"","sources":["../../src/routes/admin.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGjC,OAAO,KAAK,EAGV,kBAAkB,EAKnB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGpE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AAQxD;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,iBAAiB,CAC/B,MAAM,EAAE,kBAAkB,EAC1B,aAAa,EAAE,aAAa,EAC5B,KAAK,EAAE,iBAAiB,EACxB,YAAY,CAAC,EAAE,YAAY,GAC1B,MAAM,GAAG,IAAI,CAmWf"}
@@ -2,7 +2,12 @@ import { Router } from 'express';
2
2
  import type { Entitlement, EntitlementStatus, OneSubServerConfig, PurchaseInfo, SubscriptionInfo } from '@onesub/shared';
3
3
  import type { PurchaseStore, SubscriptionStore } from '../store.js';
4
4
  /**
5
- * Evaluate a single entitlement for a user.
5
+ * Evaluate one entitlement against records the caller already holds.
6
+ *
7
+ * This is the whole decision, with no store access — so a caller evaluating N
8
+ * entitlements for one user reads that user's records ONCE and calls this N
9
+ * times, instead of paying 2N store round-trips. `evaluateEntitlement` below
10
+ * is the convenience wrapper for the single-entitlement case.
6
11
  *
7
12
  * A user is entitled when EITHER condition holds for any productId in
8
13
  * `entitlement.productIds`:
@@ -16,6 +21,18 @@ import type { PurchaseStore, SubscriptionStore } from '../store.js';
16
21
  * Subscription is preferred over purchase when both match (subs carry an
17
22
  * expiry; non-consumables are forever) so the source field skews toward the
18
23
  * more "interesting" signal for ops/analytics.
24
+ *
25
+ * `now` is injected so a batch evaluation scores every entitlement against one
26
+ * consistent instant rather than drifting across awaits.
27
+ */
28
+ export declare function evaluateEntitlementFrom(subs: readonly SubscriptionInfo[], purchases: readonly PurchaseInfo[], entitlement: Entitlement, now?: number): EntitlementStatus;
29
+ /**
30
+ * Evaluate a single entitlement for a user, reading the records it needs.
31
+ *
32
+ * Thin wrapper over `evaluateEntitlementFrom` — see there for the entitlement
33
+ * rules. Evaluating several entitlements for the same user through this
34
+ * function costs 2 store reads each; prefer reading once and calling
35
+ * `evaluateEntitlementFrom` directly in that case.
19
36
  */
20
37
  export declare function evaluateEntitlement(userId: string, entitlement: Entitlement, store: SubscriptionStore, purchaseStore: PurchaseStore): Promise<EntitlementStatus>;
21
38
  export declare function createEntitlementRouter(config: OneSubServerConfig, store: SubscriptionStore, purchaseStore: PurchaseStore): Router | null;
@@ -1 +1 @@
1
- {"version":3,"file":"entitlements.d.ts","sourceRoot":"","sources":["../../src/routes/entitlements.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGjC,OAAO,KAAK,EACV,WAAW,EAEX,iBAAiB,EAGjB,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAIpE;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,WAAW,EACxB,KAAK,EAAE,iBAAiB,EACxB,aAAa,EAAE,aAAa,GAC3B,OAAO,CAAC,iBAAiB,CAAC,CAkC5B;AAKD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,iBAAiB,EACxB,aAAa,EAAE,aAAa,GAC3B,MAAM,GAAG,IAAI,CA0Ef;AAID,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,YAAY,EAAE,gBAAgB,EAAE,CAAC"}
1
+ {"version":3,"file":"entitlements.d.ts","sourceRoot":"","sources":["../../src/routes/entitlements.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGjC,OAAO,KAAK,EACV,WAAW,EAEX,iBAAiB,EAGjB,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAIpE;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,uBAAuB,CACrC,IAAI,EAAE,SAAS,gBAAgB,EAAE,EACjC,SAAS,EAAE,SAAS,YAAY,EAAE,EAClC,WAAW,EAAE,WAAW,EACxB,GAAG,GAAE,MAAmB,GACvB,iBAAiB,CA+BnB;AAED;;;;;;;GAOG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,MAAM,EACd,WAAW,EAAE,WAAW,EACxB,KAAK,EAAE,iBAAiB,EACxB,aAAa,EAAE,aAAa,GAC3B,OAAO,CAAC,iBAAiB,CAAC,CAW5B;AAKD,wBAAgB,uBAAuB,CACrC,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,iBAAiB,EACxB,aAAa,EAAE,aAAa,GAC3B,MAAM,GAAG,IAAI,CAgFf;AAID,YAAY,EAAE,WAAW,EAAE,iBAAiB,EAAE,YAAY,EAAE,gBAAgB,EAAE,CAAC"}
@@ -1,6 +1,8 @@
1
1
  import { Router } from 'express';
2
2
  import type { OneSubServerConfig, PurchaseInfo, SubscriptionInfo } from '@onesub/shared';
3
3
  import type { PurchaseStore, SubscriptionStore } from '../store.js';
4
+ /** Freshness bound when the host does not configure one. */
5
+ export declare const DEFAULT_METRICS_CACHE_TTL_SECONDS = 30;
4
6
  /**
5
7
  * Read-only aggregate metrics endpoints — gated behind `config.adminSecret`.
6
8
  * Returns count-based metrics only (active count, started count, expired count).
@@ -9,9 +11,12 @@ import type { PurchaseStore, SubscriptionStore } from '../store.js';
9
11
  * server doesn't currently track; deferred to a follow-up release that adds
10
12
  * `config.products: { 'pro_monthly': { price: 9.99, currency: 'USD' } }`.
11
13
  *
12
- * Aggregation strategy: pulls every record via `store.listAll()` and reduces
13
- * in memory. Fine up to ~100k records; large deployments should optimise the
14
- * Postgres path with SQL aggregates (separate PR).
14
+ * Aggregation strategy: reads every record via `store.listAll()` and reduces in
15
+ * memory (see `metrics-aggregate.ts` for the reduction, which is where the
16
+ * semantics are tested). That is one full read per computed response, so
17
+ * responses are cached for `config.metricsCacheTtlSeconds` to bound how often
18
+ * it happens — see `metrics-cache.ts`. Removing the per-response scan entirely
19
+ * needs SQL-side aggregation, which is per-store work and still pending.
15
20
  */
16
21
  export declare function createMetricsRouter(config: OneSubServerConfig, store: SubscriptionStore, purchaseStore: PurchaseStore): Router | null;
17
22
  export type { PurchaseInfo, SubscriptionInfo };
@@ -1 +1 @@
1
- {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/routes/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGjC,OAAO,KAAK,EAIV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAOxB,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAOpE;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,iBAAiB,EACxB,aAAa,EAAE,aAAa,GAC3B,MAAM,GAAG,IAAI,CA0Sf;AAED,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,CAAC"}
1
+ {"version":3,"file":"metrics.d.ts","sourceRoot":"","sources":["../../src/routes/metrics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGjC,OAAO,KAAK,EAIV,kBAAkB,EAClB,YAAY,EACZ,gBAAgB,EACjB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAepE,4DAA4D;AAC5D,eAAO,MAAM,iCAAiC,KAAK,CAAC;AAEpD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,kBAAkB,EAC1B,KAAK,EAAE,iBAAiB,EACxB,aAAa,EAAE,aAAa,GAC3B,MAAM,GAAG,IAAI,CAuKf;AAED,YAAY,EAAE,YAAY,EAAE,gBAAgB,EAAE,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"purchase.d.ts","sourceRoot":"","sources":["../../src/routes/purchase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGjC,OAAO,KAAK,EAIV,kBAAkB,EACnB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA4BjD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,kBAAkB,EAC1B,aAAa,EAAE,aAAa,GAC3B,MAAM,CA+PR"}
1
+ {"version":3,"file":"purchase.d.ts","sourceRoot":"","sources":["../../src/routes/purchase.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AAGjC,OAAO,KAAK,EAIV,kBAAkB,EACnB,MAAM,gBAAgB,CAAC;AAExB,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAgDjD,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,kBAAkB,EAC1B,aAAa,EAAE,aAAa,GAC3B,MAAM,CAkQR"}
package/dist/store.d.ts CHANGED
@@ -54,8 +54,9 @@ export interface SubscriptionStore {
54
54
  * see all of a user's active subscriptions to decide whether any of them
55
55
  * grants the requested entitlement.
56
56
  *
57
- * Implementations: in-memory currently coalesces by userId so this returns
58
- * at most one row; Postgres returns the full history.
57
+ * All three built-in implementations return the user's full set, one record
58
+ * per `originalTransactionId`. "Most-recent-first" is last-written-first for
59
+ * the in-memory store and `updated_at DESC` for Postgres/Redis.
59
60
  */
60
61
  getAllByUserId(userId: string): Promise<SubscriptionInfo[]>;
61
62
  }
@@ -78,7 +79,28 @@ export declare class InMemorySubscriptionStore implements SubscriptionStore {
78
79
  */
79
80
  export interface PurchaseStore {
80
81
  savePurchase(purchase: PurchaseInfo): Promise<void>;
82
+ /**
83
+ * Every purchase for the user, most-recent-first (by `purchasedAt`).
84
+ *
85
+ * Unbounded: a user with a long consumable history has a long row set. Prefer
86
+ * `getPurchasesForProduct` when only one product matters.
87
+ */
81
88
  getPurchasesByUserId(userId: string): Promise<PurchaseInfo[]>;
89
+ /**
90
+ * Purchases for one `userId` + `productId`, most-recent-first.
91
+ *
92
+ * OPTIONAL. When a store does not implement it, callers fall back to
93
+ * `getPurchasesByUserId` and filter in process — correct, but it transfers
94
+ * every row the user has. That is the hot path for non-consumable validation:
95
+ * a user who has bought thousands of consumables paid for all of them on every
96
+ * lifetime-product purchase. Implement this and the same answer comes from an
97
+ * index (`WHERE user_id = $1 AND product_id = $2` in Postgres, the
98
+ * `user_product` set in Redis).
99
+ *
100
+ * Optional rather than required so existing custom `PurchaseStore`
101
+ * implementations keep compiling; all three built-in stores implement it.
102
+ */
103
+ getPurchasesForProduct?(userId: string, productId: string): Promise<PurchaseInfo[]>;
82
104
  getPurchaseByTransactionId(txId: string): Promise<PurchaseInfo | null>;
83
105
  /** Returns every purchase record. Used by metrics aggregation; admin-gated. */
84
106
  listAll(): Promise<PurchaseInfo[]>;
@@ -117,6 +139,7 @@ export declare class InMemoryPurchaseStore implements PurchaseStore {
117
139
  private readonly byUserId;
118
140
  savePurchase(purchase: PurchaseInfo): Promise<void>;
119
141
  getPurchasesByUserId(userId: string): Promise<PurchaseInfo[]>;
142
+ getPurchasesForProduct(userId: string, productId: string): Promise<PurchaseInfo[]>;
120
143
  getPurchaseByTransactionId(txId: string): Promise<PurchaseInfo | null>;
121
144
  hasPurchased(userId: string, productId: string): Promise<boolean>;
122
145
  reassignPurchase(transactionId: string, newUserId: string): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"store.d.ts","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
@@ -80,6 +80,16 @@ export declare class PostgresPurchaseStore implements PurchaseStore {
80
80
  initSchema(): Promise<void>;
81
81
  savePurchase(purchase: PurchaseInfo): Promise<void>;
82
82
  getPurchasesByUserId(userId: string): Promise<PurchaseInfo[]>;
83
+ /**
84
+ * Purchases for one user + product, most-recent-first.
85
+ *
86
+ * Same shape and ordering as `getPurchasesByUserId`, with `product_id` pushed
87
+ * into the WHERE clause instead of filtered in process. Non-consumables also
88
+ * have a partial unique index on `(user_id, product_id)`, so the ownership
89
+ * check this backs is a single index lookup rather than a full read of the
90
+ * user's purchase history.
91
+ */
92
+ getPurchasesForProduct(userId: string, productId: string): Promise<PurchaseInfo[]>;
83
93
  getPurchaseByTransactionId(txId: string): Promise<PurchaseInfo | null>;
84
94
  hasPurchased(userId: string, productId: string): Promise<boolean>;
85
95
  reassignPurchase(transactionId: string, newUserId: string): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../src/stores/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACrE,OAAO,KAAK,EACV,iBAAiB,EACjB,aAAa,EACb,mBAAmB,EACnB,kBAAkB,EACnB,MAAM,aAAa,CAAC;AA4BrB;;;;;;;;;;;;;GAaG;AACH,qBAAa,yBAA0B,YAAW,iBAAiB;IAIrD,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAF7C,OAAO,CAAC,WAAW,CAA2C;gBAEjC,gBAAgB,EAAE,MAAM;IAErD,OAAO,CAAC,OAAO;IAOf;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAOjC;;;;OAIG;IACG,IAAI,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkChD;;;;;;OAMG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAanE;;;;;OAKG;IACG,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAYjE;;;OAGG;IACG,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAWlE,OAAO,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAMtC,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAkD1E,uDAAuD;IACjD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAO7B;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,qBAAsB,YAAW,aAAa;IAG7C,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAF7C,OAAO,CAAC,WAAW,CAA2C;gBAEjC,gBAAgB,EAAE,MAAM;IAErD,OAAO,CAAC,OAAO;IAOf;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAO3B,YAAY,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IA+CnD,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAY7D,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAWtE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAYjE,gBAAgB,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAS5E,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IASnE,6BAA6B,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAStE,OAAO,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAMxC,uDAAuD;IACjD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAO7B"}
1
+ {"version":3,"file":"postgres.d.ts","sourceRoot":"","sources":["../../src/stores/postgres.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AACrE,OAAO,KAAK,EACV,iBAAiB,EACjB,aAAa,EACb,mBAAmB,EACnB,kBAAkB,EACnB,MAAM,aAAa,CAAC;AA4BrB;;;;;;;;;;;;;GAaG;AACH,qBAAa,yBAA0B,YAAW,iBAAiB;IAIrD,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAF7C,OAAO,CAAC,WAAW,CAA2C;gBAEjC,gBAAgB,EAAE,MAAM;IAErD,OAAO,CAAC,OAAO;IAOf;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAOjC;;;;OAIG;IACG,IAAI,CAAC,GAAG,EAAE,gBAAgB,GAAG,OAAO,CAAC,IAAI,CAAC;IAkChD;;;;;;OAMG;IACG,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAanE;;;;;OAKG;IACG,cAAc,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAYjE;;;OAGG;IACG,kBAAkB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,GAAG,IAAI,CAAC;IAWlE,OAAO,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAMtC,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC;IAkD1E,uDAAuD;IACjD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAO7B;AAED;;;;;;;;;;;;GAYG;AACH,qBAAa,qBAAsB,YAAW,aAAa;IAG7C,OAAO,CAAC,QAAQ,CAAC,gBAAgB;IAF7C,OAAO,CAAC,WAAW,CAA2C;gBAEjC,gBAAgB,EAAE,MAAM;IAErD,OAAO,CAAC,OAAO;IAOf;;;OAGG;IACG,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC;IAO3B,YAAY,CAAC,QAAQ,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC;IA+CnD,oBAAoB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAYnE;;;;;;;;OAQG;IACG,sBAAsB,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC;IAYlF,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC;IAWtE,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAYjE,gBAAgB,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAS5E,eAAe,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IASnE,6BAA6B,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAStE,OAAO,IAAI,OAAO,CAAC,YAAY,EAAE,CAAC;IAMxC,uDAAuD;IACjD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;CAO7B"}
@@ -18,6 +18,18 @@ export declare class RedisPurchaseStore implements PurchaseStore {
18
18
  constructor(redis: IORedis);
19
19
  savePurchase(purchase: PurchaseInfo): Promise<void>;
20
20
  getPurchasesByUserId(userId: string): Promise<PurchaseInfo[]>;
21
+ /**
22
+ * Purchases for one user + product, most-recent-first.
23
+ *
24
+ * Served from the `user_product` set that `savePurchase` already maintains for
25
+ * `hasPurchased`, so this reads only the rows for this product rather than the
26
+ * user's whole purchase history.
27
+ *
28
+ * That set is unordered — unlike the per-user sorted set — so the
29
+ * most-recent-first contract is restored by an explicit sort here. The row
30
+ * count is per-product, so this is small.
31
+ */
32
+ getPurchasesForProduct(userId: string, productId: string): Promise<PurchaseInfo[]>;
21
33
  getPurchaseByTransactionId(txId: string): Promise<PurchaseInfo | null>;
22
34
  hasPurchased(userId: string, productId: string): Promise<boolean>;
23
35
  reassignPurchase(transactionId: string, newUserId: string): Promise<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../src/stores/redis.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"redis.d.ts","sourceRoot":"","sources":["../../src/stores/redis.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@onesub/server",
3
- "version": "0.19.0",
3
+ "version": "0.21.0",
4
4
  "description": "Server-side receipt validation middleware for react-native-iap. Apple StoreKit 2 + Google Play Billing. One line.",
5
5
  "main": "dist/index.cjs",
6
6
  "module": "dist/index.js",
@@ -55,7 +55,7 @@
55
55
  },
56
56
  "license": "MIT",
57
57
  "dependencies": {
58
- "@onesub/shared": "0.14.0",
58
+ "@onesub/shared": "0.15.0",
59
59
  "jose": "^6.2.3",
60
60
  "zod": "^4.4.3"
61
61
  },