@vxil/sdk 0.7.0 → 0.8.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.
package/dist/index.d.ts CHANGED
@@ -535,6 +535,33 @@ export interface PaymentsEntitlementView {
535
535
  period_end: string | null;
536
536
  };
537
537
  }
538
+ /** The provider's own state of a charge, as a closed union (2026-09-25). Paddle
539
+ * delivers `paid` (money taken) and then `completed` (fulfilled) for ONE
540
+ * charge; every other provider has a single terminal state and lands
541
+ * `completed` at once. `payments.charge.succeeded` carries the state of the
542
+ * delivery that recorded the charge; `payments.charge.completed` fires at
543
+ * most once per charge, only when a recorded charge later TRANSITIONS to
544
+ * `completed` (Paddle paid → completed). Handle `succeeded` gated on
545
+ * `provider_status === 'completed'` plus `completed` to act once per charge. */
546
+ export type PaymentsProviderChargeStatus = 'paid' | 'completed';
547
+ /** A charge row as listed by GET /v1/payments/charges (payments.md §3). */
548
+ export interface PaymentsCharge {
549
+ /** the vxil charge id (`chg_…`) — what refunds and a charge-linked grant key on */
550
+ charge_id: string;
551
+ end_user_id: string;
552
+ provider: string;
553
+ /** the provider's own transaction / payment id */
554
+ provider_charge_id: string | null;
555
+ /** null on rows recorded before the provider-state column existed */
556
+ provider_status: PaymentsProviderChargeStatus | null;
557
+ /** the one-off product the charge bought (the `ledger.productMap` key), if any */
558
+ product_id: string | null;
559
+ amount_cents: number;
560
+ amount_refunded: number;
561
+ currency: string;
562
+ status: string;
563
+ created_at: string;
564
+ }
538
565
  export interface FileObject {
539
566
  object_id: string;
540
567
  filename: string;
@@ -1513,7 +1540,12 @@ export interface CmsReindexPage {
1513
1540
  /** The result of a bounded filtered delete (cms.md §20). `matched` counts the
1514
1541
  * rows this page selected (≤ `limit`); `deleted` counts the ones actually
1515
1542
  * removed (a row that vanished between the match and the delete is skipped).
1516
- * Loop while `deleted > 0` — the filter re-evaluates against live rows. */
1543
+ * Loop while `deleted > 0` — the filter re-evaluates against live rows.
1544
+ * `complete` is false when the call stopped starting rows at its wall-clock
1545
+ * bound (8 s; the row in flight finishes) before every matched row was
1546
+ * processed — such a call always deleted at least one row, so the loop above
1547
+ * stays exact; `next_cursor` resumes that page if you prefer. Rows already
1548
+ * deleted stay deleted. */
1517
1549
  export interface CmsBulkDeleteResult {
1518
1550
  collection: string;
1519
1551
  matched: number;
@@ -1521,6 +1553,8 @@ export interface CmsBulkDeleteResult {
1521
1553
  cascaded: number;
1522
1554
  set_null: number;
1523
1555
  dry_run: boolean;
1556
+ /** every matched row was processed (always true for a dry run). */
1557
+ complete: boolean;
1524
1558
  next_cursor: string | null;
1525
1559
  }
1526
1560
  /** A write guard (cms.md §10): "after this write, at most `max` live items
@@ -1959,6 +1993,24 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1959
1993
  delete: (id: string, opts?: {
1960
1994
  erase?: boolean;
1961
1995
  }) => Promise<void>;
1996
+ /**
1997
+ * Merge a REGISTRY-ONLY user (a row you created with upsert that never
1998
+ * signed in) INTO another user of the tenant: its attributes fill the
1999
+ * survivor's gaps (the survivor wins on conflicts), the merged id is
2000
+ * soft-deleted, its owner-scoped cms / files / payments rows move to the
2001
+ * survivor, and `auth.user.merged { from, into, method: 'registry_merge' }`
2002
+ * is audited. `rekeyed: false` means a re-key could not finish inside the
2003
+ * request — the event is the backstop (call the feature's re-key route
2004
+ * again; it is idempotent). 409 `has_auth_identity` when the id already
2005
+ * has a sign-in identity: merge that through the auth flows instead.
2006
+ * Server-only (403 in end-user mode). Scope users:write.
2007
+ */
2008
+ merge: (id: string, into: string) => Promise<{
2009
+ from: string;
2010
+ into: string;
2011
+ merged: boolean;
2012
+ rekeyed: boolean;
2013
+ }>;
1962
2014
  list: (q?: {
1963
2015
  email?: string;
1964
2016
  /** substring match on email + display name (server-side, trgm-indexed), 1-100 chars */
@@ -2086,6 +2138,8 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
2086
2138
  * `counts.total / sent` to 4 dp, or null when nothing was sent. `sent`
2087
2139
  * counts deliveries the provider ACCEPTED, not inbox-delivered mail —
2088
2140
  * this is a suppressions-per-accepted-send ratio, not an RFC bounce rate.
2141
+ * `sent` is counted up to 100 000: past that it is the cap and
2142
+ * `sent_capped` is true (the rate is then a lower bound).
2089
2143
  * Operator surface: a verified end-user token gets 403. */
2090
2144
  stats: (q?: {
2091
2145
  days?: number;
@@ -2100,6 +2154,7 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
2100
2154
  total: number;
2101
2155
  };
2102
2156
  sent: number;
2157
+ sent_capped?: boolean;
2103
2158
  rate: number | null;
2104
2159
  }>;
2105
2160
  };
@@ -3132,7 +3187,10 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
3132
3187
  * the SAME single-item delete path (restrict refusal, cascade budget,
3133
3188
  * unique-claim freeing, CDC), so this is N deletes in one round trip, not
3134
3189
  * a set-delete. `dryRun` reports `matched` and mutates nothing. Loop
3135
- * while `deleted > 0`. A refusal mid-sweep (409 referenced / 422
3190
+ * while `deleted > 0`. A call stops STARTING rows after 8 s of wall
3191
+ * clock (the row in flight finishes; never before its first delete) and
3192
+ * answers `complete: false` + `next_cursor` (pass it as `cursor` to
3193
+ * resume the page). A refusal mid-sweep (409 referenced / 422
3136
3194
  * cascade_too_large) stops the call — rows already deleted STAY deleted,
3137
3195
  * and the error message names how many. Soft-deleted rows are hard-purged
3138
3196
  * by the platform 30 days later (there is no per-tenant retention knob). */
@@ -3677,6 +3735,21 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
3677
3735
  state: string;
3678
3736
  }>>;
3679
3737
  unsubscribe: (subId: string) => Promise<void>;
3738
+ /**
3739
+ * Change a subscription's event filter in place (2026-09-25). The sub_id
3740
+ * and the delivery cursor are kept — nothing is replayed, nothing is
3741
+ * skipped; the new prefixes apply from the next delivery pass. An empty
3742
+ * array subscribes to every event. `target_url` is the subscription's
3743
+ * identity and is not editable: a new target is a new subscription.
3744
+ */
3745
+ update: (subId: string, input: {
3746
+ event_prefixes: string[];
3747
+ }) => Promise<{
3748
+ sub_id: string;
3749
+ target_url: string;
3750
+ event_prefixes: string[];
3751
+ state: string;
3752
+ }>;
3680
3753
  /**
3681
3754
  * Send a synthetic 'webhooks.test' event to the subscription's endpoint
3682
3755
  * through the REAL signing+delivery pipeline and wait inline (≤ ~8 s).
@@ -4794,19 +4867,29 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
4794
4867
  * entitlements + the tier's recurring credits apply; `until` null =
4795
4868
  * open-ended, else access ends at `until`. Idempotent on
4796
4869
  * `idempotency_key` (a replay returns the first grant, `replayed: true`).
4797
- * 422 unknown_tier when the tier is not in ledger.tierMap. */
4870
+ * 422 unknown_tier when the tier is not in ledger.tierMap.
4871
+ * `charge_id` (optional) links the grant to the vxil charge (`chg_…`, from
4872
+ * `listCharges` or the `payments.charge.succeeded` / `.completed` event)
4873
+ * it was bought with: a FULL refund or chargeback of that charge ends the
4874
+ * grant (`payments.subscription.revoked` with `via: 'refund' | 'chargeback'`).
4875
+ * 404 charge_not_found; 422 charge_user_mismatch (another user's charge) /
4876
+ * charge_refunded (the refund beat the grant — nothing to grant against).
4877
+ * 422 reserved_idempotency_key when `idempotency_key` starts with
4878
+ * `purchase:` (the namespace of purchase passes). */
4798
4879
  grantSubscription: (input: {
4799
4880
  user_id: string;
4800
4881
  tier: string;
4801
4882
  until?: string | null;
4802
4883
  reason: string;
4803
4884
  idempotency_key: string;
4885
+ charge_id?: string | null;
4804
4886
  }) => Promise<{
4805
4887
  subscription_id: string | null;
4806
4888
  user_id: string;
4807
4889
  tier: string;
4808
4890
  status: string;
4809
4891
  until: string | null;
4892
+ charge_id: string | null;
4810
4893
  replayed: boolean;
4811
4894
  }>;
4812
4895
  /** SERVER-ONLY. Revoke a MANUAL grant now (409 not_manual for a provider
@@ -4884,22 +4967,18 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
4884
4967
  } | null;
4885
4968
  }>;
4886
4969
  /** List charges newest-first (the refund enabler — discover the charge_id).
4887
- * Filters: user_id, status, limit (clamped 1..100, default 50). */
4970
+ * Filters: user_id, status, limit (clamped 1..100, default 50).
4971
+ * Each row carries the provider's own charge id (`provider_charge_id`),
4972
+ * the provider's charge state (`provider_status`: `'paid'` — Paddle's
4973
+ * money-taken state — or `'completed'`; null on rows recorded before
4974
+ * 2026-09-25) and the one-off `product_id` it bought, so a function can
4975
+ * match a charge to a provider event without reading the delivery log. */
4888
4976
  listCharges: (q?: {
4889
4977
  user_id?: string;
4890
4978
  status?: "succeeded" | "failed" | "refunded" | "partially_refunded" | "pending";
4891
4979
  limit?: number;
4892
4980
  }) => Promise<{
4893
- charges: Array<{
4894
- charge_id: string;
4895
- end_user_id: string;
4896
- provider: string;
4897
- amount_cents: number;
4898
- amount_refunded: number;
4899
- currency: string;
4900
- status: string;
4901
- created_at: string;
4902
- }>;
4981
+ charges: PaymentsCharge[];
4903
4982
  }>;
4904
4983
  /** Provider webhook event log (payments.md §7 "Event log & replay"):
4905
4984
  * operator visibility over every delivery — incl. persisted signature
package/dist/index.js CHANGED
@@ -400,6 +400,19 @@ export class Vxil {
400
400
  delete: async (id, opts) => {
401
401
  await this.call('DELETE', `/v1/users/${encodeURIComponent(id)}${opts?.erase ? '?erase=true' : ''}`);
402
402
  },
403
+ /**
404
+ * Merge a REGISTRY-ONLY user (a row you created with upsert that never
405
+ * signed in) INTO another user of the tenant: its attributes fill the
406
+ * survivor's gaps (the survivor wins on conflicts), the merged id is
407
+ * soft-deleted, its owner-scoped cms / files / payments rows move to the
408
+ * survivor, and `auth.user.merged { from, into, method: 'registry_merge' }`
409
+ * is audited. `rekeyed: false` means a re-key could not finish inside the
410
+ * request — the event is the backstop (call the feature's re-key route
411
+ * again; it is idempotent). 409 `has_auth_identity` when the id already
412
+ * has a sign-in identity: merge that through the auth flows instead.
413
+ * Server-only (403 in end-user mode). Scope users:write.
414
+ */
415
+ merge: async (id, into) => (await this.call('POST', `/v1/users/${encodeURIComponent(id)}/merge`, { into })).data,
403
416
  list: async (q) => {
404
417
  const s = qs({
405
418
  email: q?.email || undefined,
@@ -460,6 +473,8 @@ export class Vxil {
460
473
  * `counts.total / sent` to 4 dp, or null when nothing was sent. `sent`
461
474
  * counts deliveries the provider ACCEPTED, not inbox-delivered mail —
462
475
  * this is a suppressions-per-accepted-send ratio, not an RFC bounce rate.
476
+ * `sent` is counted up to 100 000: past that it is the cap and
477
+ * `sent_capped` is true (the rate is then a lower bound).
463
478
  * Operator surface: a verified end-user token gets 403. */
464
479
  stats: async (q) => {
465
480
  const s = qs({ days: q?.days || undefined });
@@ -1170,7 +1185,10 @@ export class Vxil {
1170
1185
  * the SAME single-item delete path (restrict refusal, cascade budget,
1171
1186
  * unique-claim freeing, CDC), so this is N deletes in one round trip, not
1172
1187
  * a set-delete. `dryRun` reports `matched` and mutates nothing. Loop
1173
- * while `deleted > 0`. A refusal mid-sweep (409 referenced / 422
1188
+ * while `deleted > 0`. A call stops STARTING rows after 8 s of wall
1189
+ * clock (the row in flight finishes; never before its first delete) and
1190
+ * answers `complete: false` + `next_cursor` (pass it as `cursor` to
1191
+ * resume the page). A refusal mid-sweep (409 referenced / 422
1174
1192
  * cascade_too_large) stops the call — rows already deleted STAY deleted,
1175
1193
  * and the error message names how many. Soft-deleted rows are hard-purged
1176
1194
  * by the platform 30 days later (there is no per-tenant retention knob). */
@@ -1439,6 +1457,14 @@ export class Vxil {
1439
1457
  unsubscribe: async (subId) => {
1440
1458
  await this.call('DELETE', `/v1/webhooks/subscriptions/${encodeURIComponent(subId)}`);
1441
1459
  },
1460
+ /**
1461
+ * Change a subscription's event filter in place (2026-09-25). The sub_id
1462
+ * and the delivery cursor are kept — nothing is replayed, nothing is
1463
+ * skipped; the new prefixes apply from the next delivery pass. An empty
1464
+ * array subscribes to every event. `target_url` is the subscription's
1465
+ * identity and is not editable: a new target is a new subscription.
1466
+ */
1467
+ update: async (subId, input) => (await this.call('PATCH', `/v1/webhooks/subscriptions/${encodeURIComponent(subId)}`, input)).data,
1442
1468
  /**
1443
1469
  * Send a synthetic 'webhooks.test' event to the subscription's endpoint
1444
1470
  * through the REAL signing+delivery pipeline and wait inline (≤ ~8 s).
@@ -1988,7 +2014,15 @@ export class Vxil {
1988
2014
  * entitlements + the tier's recurring credits apply; `until` null =
1989
2015
  * open-ended, else access ends at `until`. Idempotent on
1990
2016
  * `idempotency_key` (a replay returns the first grant, `replayed: true`).
1991
- * 422 unknown_tier when the tier is not in ledger.tierMap. */
2017
+ * 422 unknown_tier when the tier is not in ledger.tierMap.
2018
+ * `charge_id` (optional) links the grant to the vxil charge (`chg_…`, from
2019
+ * `listCharges` or the `payments.charge.succeeded` / `.completed` event)
2020
+ * it was bought with: a FULL refund or chargeback of that charge ends the
2021
+ * grant (`payments.subscription.revoked` with `via: 'refund' | 'chargeback'`).
2022
+ * 404 charge_not_found; 422 charge_user_mismatch (another user's charge) /
2023
+ * charge_refunded (the refund beat the grant — nothing to grant against).
2024
+ * 422 reserved_idempotency_key when `idempotency_key` starts with
2025
+ * `purchase:` (the namespace of purchase passes). */
1992
2026
  grantSubscription: async (input) => (await this.call('POST', '/v1/payments/subscriptions/grant', input)).data,
1993
2027
  /** SERVER-ONLY. Revoke a MANUAL grant now (409 not_manual for a provider
1994
2028
  * subscription — use `cancel`). Never touches a provider. */
@@ -2022,7 +2056,12 @@ export class Vxil {
2022
2056
  * 403 `server_only`; the sweep is a tenant-wide operator surface. */
2023
2057
  reconcile: async () => (await this.call('GET', '/v1/payments/reconcile')).data,
2024
2058
  /** List charges newest-first (the refund enabler — discover the charge_id).
2025
- * Filters: user_id, status, limit (clamped 1..100, default 50). */
2059
+ * Filters: user_id, status, limit (clamped 1..100, default 50).
2060
+ * Each row carries the provider's own charge id (`provider_charge_id`),
2061
+ * the provider's charge state (`provider_status`: `'paid'` — Paddle's
2062
+ * money-taken state — or `'completed'`; null on rows recorded before
2063
+ * 2026-09-25) and the one-off `product_id` it bought, so a function can
2064
+ * match a charge to a provider event without reading the delivery log. */
2026
2065
  listCharges: async (q) => {
2027
2066
  const suffix = qs({
2028
2067
  user_id: q?.user_id || undefined,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vxil/sdk",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Typed client for the Vxil REST API (notifications, auth, jobs, files, cms, comments, webhooks, realtime, orgs, rate-limits).",