@vxil/sdk 0.4.0 → 0.5.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/README.md +4 -0
- package/dist/index.d.ts +411 -20
- package/dist/index.js +208 -17
- package/package.json +2 -3
package/README.md
CHANGED
|
@@ -88,6 +88,10 @@ try {
|
|
|
88
88
|
|
|
89
89
|
## Changelog
|
|
90
90
|
|
|
91
|
+
### 0.4.1 — 2026-09-13
|
|
92
|
+
|
|
93
|
+
- Documentation only: JSDoc no longer cites internal repository paths; points at the public guide instead. No runtime change.
|
|
94
|
+
|
|
91
95
|
### 0.4.0 — 2026-09-11
|
|
92
96
|
|
|
93
97
|
- React-Native-clean: every query string is built without `URLSearchParams` (React Native's polyfill throws on `.set` before 0.81); wire bytes unchanged.
|
package/dist/index.d.ts
CHANGED
|
@@ -84,11 +84,11 @@ export interface VxilOptions {
|
|
|
84
84
|
* a mobile/SPA holds a public/thin-client key + the signed-in user's session
|
|
85
85
|
* token and calls the edge directly, safely scoped to that user. Absent ⇒
|
|
86
86
|
* **no header** ⇒ server-caller mode, exactly as today (backward compatible).
|
|
87
|
-
* See docs/
|
|
87
|
+
* See https://vxil.com/docs/guide/09-security-and-multitenancy. For a per-call override on
|
|
88
88
|
* an otherwise server-mode client, use `vx.asEndUser(token)`. */
|
|
89
89
|
endUserToken?: string;
|
|
90
90
|
/** Global API major every request pins (default `'v1'`). Path-major, per
|
|
91
|
-
* docs/
|
|
91
|
+
* the versioning section of https://vxil.com/docs/guide/12-going-to-production — a breaking change ships as a new major on a
|
|
92
92
|
* new path, never in place. */
|
|
93
93
|
apiVersion?: ApiVersion;
|
|
94
94
|
/** Per-feature API-major overrides (`{ cms: 'v2' }`); each takes precedence
|
|
@@ -208,6 +208,41 @@ export interface FeatureConfig {
|
|
|
208
208
|
version: number;
|
|
209
209
|
manifest: Record<string, unknown>;
|
|
210
210
|
}
|
|
211
|
+
/** One rule in the advisory architecture brief. `title`, `fact`, `replaces` and
|
|
212
|
+
* `docs` are platform copy rendered by the server from a gate-checked rule
|
|
213
|
+
* catalog — they are never model prose. `why` explains why the rule applies to
|
|
214
|
+
* the app you described. */
|
|
215
|
+
export interface VxilBriefItem {
|
|
216
|
+
/** stable rule id, e.g. 'anonymous-session-for-guests'. */
|
|
217
|
+
rule_id: string;
|
|
218
|
+
title: string;
|
|
219
|
+
/** the platform rule itself, numbers inline. */
|
|
220
|
+
fact: string;
|
|
221
|
+
/** why it applies to this app. */
|
|
222
|
+
why: string;
|
|
223
|
+
/** the older habit this rule supersedes, when there is one. */
|
|
224
|
+
replaces?: string;
|
|
225
|
+
/** site path to the chapter section, e.g. '/docs/guide/16-…#…'. */
|
|
226
|
+
docs: string;
|
|
227
|
+
}
|
|
228
|
+
/** One section of the brief: the eight subject sections plus 'old-patterns',
|
|
229
|
+
* a derived view of every cited rule that supersedes an older habit. */
|
|
230
|
+
export interface VxilBriefSection {
|
|
231
|
+
/** 'identity' | 'data' | 'money' | 'background' | 'notifications' |
|
|
232
|
+
* 'rate-limits' | 'environments' | 'limits' | 'old-patterns' */
|
|
233
|
+
key: string;
|
|
234
|
+
title: string;
|
|
235
|
+
items: VxilBriefItem[];
|
|
236
|
+
}
|
|
237
|
+
/** The ADVISORY architecture brief attached to a plan: the platform rules the
|
|
238
|
+
* plan's shape implies. `source` is 'ai' when a model chose the rules and
|
|
239
|
+
* 'deterministic' when they were derived from the feature set alone. */
|
|
240
|
+
export interface VxilPlanBrief {
|
|
241
|
+
source: 'ai' | 'deterministic';
|
|
242
|
+
sections: VxilBriefSection[];
|
|
243
|
+
/** decisions the description does not settle (empty on the deterministic path). */
|
|
244
|
+
open_questions: string[];
|
|
245
|
+
}
|
|
211
246
|
/** An advisory plan from the planner (POST /v1/plan) — a proposal, not applied. */
|
|
212
247
|
export interface VxilPlan {
|
|
213
248
|
app: {
|
|
@@ -254,6 +289,8 @@ export interface VxilPlan {
|
|
|
254
289
|
rationale: string;
|
|
255
290
|
/** which brain produced the plan. */
|
|
256
291
|
source: 'ai' | 'deterministic';
|
|
292
|
+
/** the advisory architecture brief. Absent when you pass `{ brief: false }`. */
|
|
293
|
+
brief?: VxilPlanBrief;
|
|
257
294
|
}
|
|
258
295
|
/** Current-month usage projection (GET /v1/usage) — the same meter the
|
|
259
296
|
* platform bills and enforces request quotas from. */
|
|
@@ -281,6 +318,15 @@ export interface UsageCurrent {
|
|
|
281
318
|
metric: string;
|
|
282
319
|
quantity: number;
|
|
283
320
|
}>;
|
|
321
|
+
/** month-to-date split of `requests.used` by the SURFACE that served it —
|
|
322
|
+
* the edge's resolved upstream (a bounded set), with /v1/fn/* reported as
|
|
323
|
+
* 'fn-invoke' and /v1/functions/* as its management surface,
|
|
324
|
+
* 'control-plane'. Busiest first. Observational only: never a billing
|
|
325
|
+
* basis, and [] wherever request metering is not switched on. */
|
|
326
|
+
routes: Array<{
|
|
327
|
+
route_class: string;
|
|
328
|
+
requests: number;
|
|
329
|
+
}>;
|
|
284
330
|
}
|
|
285
331
|
export interface DeadLetter {
|
|
286
332
|
delivery_id: string;
|
|
@@ -567,6 +613,12 @@ export interface SearchSyncSource {
|
|
|
567
613
|
export interface AiUsage {
|
|
568
614
|
input_tokens: number;
|
|
569
615
|
output_tokens: number;
|
|
616
|
+
/** PROMPT-CACHE accounting, present only when the provider reports it
|
|
617
|
+
* (anthropic, when the request asked for a cache breakpoint). A cache read
|
|
618
|
+
* is already excluded from `input_tokens`, so metering and the daily budget
|
|
619
|
+
* need no adjustment — these are for your own visibility. */
|
|
620
|
+
cache_creation_input_tokens?: number;
|
|
621
|
+
cache_read_input_tokens?: number;
|
|
570
622
|
}
|
|
571
623
|
/** One tool invocation the model asked for (tool-use passthrough — vxil relays,
|
|
572
624
|
* YOU execute). Echo `id` back as `tool_call_id` on the `role:'tool'` turn. */
|
|
@@ -1074,6 +1126,12 @@ export interface VxilSchemaShape {
|
|
|
1074
1126
|
Patch: unknown;
|
|
1075
1127
|
Sortable: string;
|
|
1076
1128
|
Filterable: Record<string, unknown>;
|
|
1129
|
+
/** `vxil gen` emits this per collection: every `$expand`-able field (relation
|
|
1130
|
+
* + file) → its target collection slug (`'files'` for a file field, `null`
|
|
1131
|
+
* for a relation with no declared target). OPTIONAL on the shape so a client
|
|
1132
|
+
* generated BEFORE typed expand still satisfies the constraint — the expand
|
|
1133
|
+
* names then stay `string` and an expanded member resolves to an opaque bag. */
|
|
1134
|
+
Relations?: Record<string, string | null>;
|
|
1077
1135
|
}>;
|
|
1078
1136
|
functions: Record<string, {
|
|
1079
1137
|
Input: unknown;
|
|
@@ -1144,6 +1202,33 @@ export interface CmsBacklinksPage {
|
|
|
1144
1202
|
has_more: boolean;
|
|
1145
1203
|
limit: number;
|
|
1146
1204
|
}
|
|
1205
|
+
/** ONE page of the slot re-index (cms.md §19). Loop while `complete` is false,
|
|
1206
|
+
* feeding `next_cursor` back in. `skipped` counts rows a concurrent write moved
|
|
1207
|
+
* under the page — the re-index never overwrites them (their own writer
|
|
1208
|
+
* re-projected them), but a `$inc` or a cascade `set_null` only re-projects its
|
|
1209
|
+
* OWN field's slot, so re-run the page while `skipped > 0`. */
|
|
1210
|
+
export interface CmsReindexPage {
|
|
1211
|
+
collection: string;
|
|
1212
|
+
field: string | null;
|
|
1213
|
+
scanned: number;
|
|
1214
|
+
updated: number;
|
|
1215
|
+
skipped: number;
|
|
1216
|
+
next_cursor: string | null;
|
|
1217
|
+
complete: boolean;
|
|
1218
|
+
}
|
|
1219
|
+
/** The result of a bounded filtered delete (cms.md §20). `matched` counts the
|
|
1220
|
+
* rows this page selected (≤ `limit`); `deleted` counts the ones actually
|
|
1221
|
+
* removed (a row that vanished between the match and the delete is skipped).
|
|
1222
|
+
* Loop while `deleted > 0` — the filter re-evaluates against live rows. */
|
|
1223
|
+
export interface CmsBulkDeleteResult {
|
|
1224
|
+
collection: string;
|
|
1225
|
+
matched: number;
|
|
1226
|
+
deleted: number;
|
|
1227
|
+
cascaded: number;
|
|
1228
|
+
set_null: number;
|
|
1229
|
+
dry_run: boolean;
|
|
1230
|
+
next_cursor: string | null;
|
|
1231
|
+
}
|
|
1147
1232
|
/** A write guard (cms.md §10): "after this write, at most `max` live items
|
|
1148
1233
|
* match `filter`". Requires `lock` — an unlocked guard is racy by construction. */
|
|
1149
1234
|
export interface CmsGuard {
|
|
@@ -1241,8 +1326,56 @@ export type CmsTxStep = {
|
|
|
1241
1326
|
item_id: string;
|
|
1242
1327
|
$where?: Record<string, unknown>;
|
|
1243
1328
|
};
|
|
1244
|
-
/**
|
|
1245
|
-
|
|
1329
|
+
/** One CMS item as the API returns it — the envelope `$expand` inlines in place
|
|
1330
|
+
* of a stored relation id (cms.md §6.2). `R` is the target collection's Row. */
|
|
1331
|
+
export interface CmsItemEnvelope<R> {
|
|
1332
|
+
item_id: string;
|
|
1333
|
+
collection: string;
|
|
1334
|
+
status: string;
|
|
1335
|
+
version: number;
|
|
1336
|
+
data: R;
|
|
1337
|
+
created_at: string;
|
|
1338
|
+
updated_at: string;
|
|
1339
|
+
published_at: string | null;
|
|
1340
|
+
}
|
|
1341
|
+
/** What an expanded FILE field resolves to — a stub, never a cms row. */
|
|
1342
|
+
export interface CmsFileRef {
|
|
1343
|
+
object_id: string;
|
|
1344
|
+
$ref: 'files';
|
|
1345
|
+
}
|
|
1346
|
+
/** Every shape an expanded member can take (cms.md §6.2, all four verified in
|
|
1347
|
+
* cms-v1 core.ts): the full envelope; a `{ object_id, $ref: 'files' }` stub for
|
|
1348
|
+
* a file field; the BARE id when the expansion hit a cycle or the depth budget;
|
|
1349
|
+
* `null` when the target is soft-deleted, not visible to this caller, or owned
|
|
1350
|
+
* by another end-user. A
|
|
1351
|
+
* relation field may hold an ARRAY of ids, in which case the expansion is an
|
|
1352
|
+
* array of those same shapes — `vxil gen` types a relation as `string` and does
|
|
1353
|
+
* not carry arity, so the union includes the array form rather than inventing
|
|
1354
|
+
* a precision the model does not have. */
|
|
1355
|
+
export type CmsExpanded<R> = CmsItemEnvelope<R> | CmsFileRef | string | null | Array<CmsItemEnvelope<R> | CmsFileRef | string | null>;
|
|
1356
|
+
/** The expand-able field names of a collection: the keys of the generated
|
|
1357
|
+
* `Relations` map, or plain `string` for a client generated before it existed. */
|
|
1358
|
+
type ExpandableOf<C> = C extends {
|
|
1359
|
+
Relations: infer R;
|
|
1360
|
+
} ? (R extends Record<string, unknown> ? keyof R & string : string) : string;
|
|
1361
|
+
/** Resolve one expanded member's row type: the target collection's `Row` when
|
|
1362
|
+
* `Relations` names a cms collection in THIS schema, else an opaque bag (a file
|
|
1363
|
+
* stub, an untargeted relation, or an older generated client). */
|
|
1364
|
+
type TargetRow<C, S extends VxilSchemaShape, K extends string> = C extends {
|
|
1365
|
+
Relations: infer R;
|
|
1366
|
+
} ? (K extends keyof R ? (R[K] extends keyof S['cms'] ? S['cms'][R[K]]['Row'] : Record<string, unknown>) : Record<string, unknown>) : Record<string, unknown>;
|
|
1367
|
+
/** The Row with the expanded members re-typed. Non-expanded fields are untouched,
|
|
1368
|
+
* so a query WITHOUT `expand` keeps exactly today's type. */
|
|
1369
|
+
export type ExpandedRow<C extends {
|
|
1370
|
+
Row: unknown;
|
|
1371
|
+
}, S extends VxilSchemaShape, E extends readonly string[]> = Omit<C['Row'], E[number]> & {
|
|
1372
|
+
[K in E[number]]: CmsExpanded<TargetRow<C, S, K>>;
|
|
1373
|
+
};
|
|
1374
|
+
/** A per-collection typed handle returned by `vx.from(collection)`. `S` (the
|
|
1375
|
+
* whole generated schema) is DEFAULTED so `CollectionClient<X>` — the only
|
|
1376
|
+
* spelling that existed before typed expand — still compiles; it is what lets
|
|
1377
|
+
* an expanded relation resolve to the TARGET collection's Row. */
|
|
1378
|
+
export interface CollectionClient<C extends VxilSchemaShape['cms'][string], S extends VxilSchemaShape = VxilSchemaShape> {
|
|
1246
1379
|
create(data: C['Insert'], opts?: {
|
|
1247
1380
|
status?: 'draft' | 'published';
|
|
1248
1381
|
lock?: string;
|
|
@@ -1254,7 +1387,26 @@ export interface CollectionClient<C extends VxilSchemaShape['cms'][string]> {
|
|
|
1254
1387
|
version: number;
|
|
1255
1388
|
data: C['Row'];
|
|
1256
1389
|
}>;
|
|
1390
|
+
/** `expand` inlines the named relation/file fields (cms.md §6.2) — the names
|
|
1391
|
+
* come from the generated `Relations`, so a typo is a compile error. Bounds
|
|
1392
|
+
* (worker-clamped regardless of config): depth 1 (ceiling 2), ≤5 fields
|
|
1393
|
+
* (ceiling 25). Omit it and the result type is exactly today's `Row`. */
|
|
1394
|
+
get<E extends readonly ExpandableOf<C>[]>(itemId: string, opts: {
|
|
1395
|
+
expand: E;
|
|
1396
|
+
}): Promise<ExpandedRow<C, S, E>>;
|
|
1257
1397
|
get(itemId: string): Promise<C['Row']>;
|
|
1398
|
+
query<E extends readonly ExpandableOf<C>[]>(q: {
|
|
1399
|
+
filter?: Partial<C['Filterable']>;
|
|
1400
|
+
sort?: C['Sortable'] | `-${C['Sortable'] & string}`;
|
|
1401
|
+
limit?: number;
|
|
1402
|
+
cursor?: string;
|
|
1403
|
+
/** relation/file fields to inline — see `get`. Cannot be combined with
|
|
1404
|
+
* `count` (a separate method here; the raw API 422s when both are sent). */
|
|
1405
|
+
expand: E;
|
|
1406
|
+
}): Promise<{
|
|
1407
|
+
items: ExpandedRow<C, S, E>[];
|
|
1408
|
+
next_cursor: string | null;
|
|
1409
|
+
}>;
|
|
1258
1410
|
query(q?: {
|
|
1259
1411
|
/** Keys AND values come from the generated `Filterable`: each field's value
|
|
1260
1412
|
* is its operator union (`VxilFilterRange*` on slotted fields, equality/
|
|
@@ -1283,6 +1435,15 @@ export interface CollectionClient<C extends VxilSchemaShape['cms'][string]> {
|
|
|
1283
1435
|
cascaded: number;
|
|
1284
1436
|
set_null: number;
|
|
1285
1437
|
}>;
|
|
1438
|
+
/** cms.md §20: bounded filtered delete over the generated `Filterable` — a
|
|
1439
|
+
* filter is required, ≤100 rows per call, `dryRun` previews. Loop while
|
|
1440
|
+
* `deleted > 0`. */
|
|
1441
|
+
deleteMany(q: {
|
|
1442
|
+
filter: Partial<C['Filterable']>;
|
|
1443
|
+
limit?: number;
|
|
1444
|
+
cursor?: string;
|
|
1445
|
+
dryRun?: boolean;
|
|
1446
|
+
}): Promise<CmsBulkDeleteResult>;
|
|
1286
1447
|
publish(itemId: string): Promise<C['Row']>;
|
|
1287
1448
|
/** cms-rel B2: bounded group-by aggregate (spec §7 `groupBy()` surface).
|
|
1288
1449
|
* groupBy/field names are typed over the Row's keys; slot-existence stays a
|
|
@@ -1345,7 +1506,7 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
1345
1506
|
* client, mirroring how the edge threads the verified principal. The base
|
|
1346
1507
|
* URL, api key, fetch impl, version pins and the retry/timeout/hooks options
|
|
1347
1508
|
* are inherited unchanged; only the end-user token is (re)set. Pass a falsy token to get back a server-mode
|
|
1348
|
-
* client (drops the header). See docs/
|
|
1509
|
+
* client (drops the header). See https://vxil.com/docs/guide/09-security-and-multitenancy. */
|
|
1349
1510
|
asEndUser(endUserToken: string | undefined): Vxil<S>;
|
|
1350
1511
|
/** Build the versioned request path — the ONE place a wire path is finalized.
|
|
1351
1512
|
* Every request (call(), audit.export, the fn proxy) routes through here, so
|
|
@@ -1361,7 +1522,7 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
1361
1522
|
* generic `cms.items.*` methods — the wire calls are identical; the generated
|
|
1362
1523
|
* `VxilSchema` supplies the field types. `vx.cms.items.*` stays as the
|
|
1363
1524
|
* always-available un-generic fallback. */
|
|
1364
|
-
from<C extends keyof S['cms'] & string>(collection: C): CollectionClient<S['cms'][C]>;
|
|
1525
|
+
from<C extends keyof S['cms'] & string>(collection: C): CollectionClient<S['cms'][C], S>;
|
|
1365
1526
|
/** Typed function invoke (design §4.5). `vx.fn.<name>(payload)` POSTs to
|
|
1366
1527
|
* /v1/fn/<name>; the generated `VxilSchema` types Input/Output (Level-0
|
|
1367
1528
|
* opaque until a function declares a signature). A Proxy gives the
|
|
@@ -1415,7 +1576,8 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
1415
1576
|
readonly notifications: {
|
|
1416
1577
|
send: (input: {
|
|
1417
1578
|
user_id: string;
|
|
1418
|
-
template
|
|
1579
|
+
/** The four shipped template ids (notifications.md §7). */
|
|
1580
|
+
template: "magic-link" | "otp-code" | "welcome" | "transactional";
|
|
1419
1581
|
data: Record<string, unknown>;
|
|
1420
1582
|
/** Explicit wins; otherwise the recipient's stored `locale` attribute
|
|
1421
1583
|
* (set it via `users.upsert({ attributes: { locale } })`), then
|
|
@@ -1434,7 +1596,7 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
1434
1596
|
* + enqueued, or neither) and `results` is index-aligned with `items`. */
|
|
1435
1597
|
sendBatch: (items: Array<{
|
|
1436
1598
|
user_id: string;
|
|
1437
|
-
template: "magic-link" | "welcome" | "transactional";
|
|
1599
|
+
template: "magic-link" | "otp-code" | "welcome" | "transactional";
|
|
1438
1600
|
data: Record<string, unknown>;
|
|
1439
1601
|
locale?: string;
|
|
1440
1602
|
channel?: "email" | "inbox" | "both";
|
|
@@ -1521,6 +1683,80 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
1521
1683
|
}>>;
|
|
1522
1684
|
add: (email: string) => Promise<void>;
|
|
1523
1685
|
remove: (email: string) => Promise<void>;
|
|
1686
|
+
/** Bounce/complaint AGGREGATE over a 1–90 day window (default 30; an
|
|
1687
|
+
* out-of-range or non-numeric `days` clamps rather than erroring).
|
|
1688
|
+
* `counts` always carries all four reasons with explicit zeros; `rate` is
|
|
1689
|
+
* `counts.total / sent` to 4 dp, or null when nothing was sent. `sent`
|
|
1690
|
+
* counts deliveries the provider ACCEPTED, not inbox-delivered mail —
|
|
1691
|
+
* this is a suppressions-per-accepted-send ratio, not an RFC bounce rate.
|
|
1692
|
+
* Operator surface: a verified end-user token gets 403. */
|
|
1693
|
+
stats: (q?: {
|
|
1694
|
+
days?: number;
|
|
1695
|
+
}) => Promise<{
|
|
1696
|
+
window_days: number;
|
|
1697
|
+
since: string;
|
|
1698
|
+
counts: {
|
|
1699
|
+
hard_bounce: number;
|
|
1700
|
+
spam_complaint: number;
|
|
1701
|
+
soft_bounce: number;
|
|
1702
|
+
manual: number;
|
|
1703
|
+
total: number;
|
|
1704
|
+
};
|
|
1705
|
+
sent: number;
|
|
1706
|
+
rate: number | null;
|
|
1707
|
+
}>;
|
|
1708
|
+
};
|
|
1709
|
+
/** Per-end-user MUTES — the opt-out table every send channel checks before
|
|
1710
|
+
* it delivers. Absence of a row means "not muted". A row with no
|
|
1711
|
+
* `template_id` is the ALL-TEMPLATES row.
|
|
1712
|
+
*
|
|
1713
|
+
* MUTE WINS. Enforcement is `(template_id = <template> OR template_id IS
|
|
1714
|
+
* NULL) AND muted`, so the all-templates row SHADOWS a per-template
|
|
1715
|
+
* `muted: false` — to re-enable one template after a master mute, clear the
|
|
1716
|
+
* all-templates row (`{ muted: false }`, no `template_id`) and mute the
|
|
1717
|
+
* others individually. `get` returns a computed `effective` per row, which
|
|
1718
|
+
* is the answer enforcement gives; render THAT, not the stored `muted`.
|
|
1719
|
+
*
|
|
1720
|
+
* AUTH MAIL IS NEVER MUTEABLE. `magic-link` and `otp-code` carry sign-in
|
|
1721
|
+
* links and codes: `set` rejects them with a 422 and the all-templates row
|
|
1722
|
+
* does not cover them, so an "unsubscribe from everything" click can never
|
|
1723
|
+
* lock a user out of its own account. Muteable ids: `welcome`,
|
|
1724
|
+
* `transactional`. (Password-reset mail rides `transactional`, which stays
|
|
1725
|
+
* muteable — see the notifications feature doc §6c.)
|
|
1726
|
+
*
|
|
1727
|
+
* SCOPES: with an end-user token both calls are confined to the verified
|
|
1728
|
+
* principal and take `notifications:read` (a browser key that can mute its
|
|
1729
|
+
* own mail must not also be able to send mail). With a server key
|
|
1730
|
+
* `user_id` is required and `set` takes `notifications:send`. */
|
|
1731
|
+
preferences: {
|
|
1732
|
+
get: (q: {
|
|
1733
|
+
user_id?: string;
|
|
1734
|
+
}) => Promise<{
|
|
1735
|
+
preferences: Array<{
|
|
1736
|
+
user_id: string;
|
|
1737
|
+
template_id: string | null;
|
|
1738
|
+
muted: boolean;
|
|
1739
|
+
/** what enforcement actually does with this row: the stored `muted`
|
|
1740
|
+
* OR the all-templates row's mute (which shadows it). */
|
|
1741
|
+
effective: boolean;
|
|
1742
|
+
updated_at: string;
|
|
1743
|
+
}>;
|
|
1744
|
+
count: number;
|
|
1745
|
+
}>;
|
|
1746
|
+
/** Idempotent upsert. OMIT `template_id` for the all-templates row; pass
|
|
1747
|
+
* a MUTEABLE id to mute just that template. Auth mail (`magic-link`,
|
|
1748
|
+
* `otp-code`) is a 422 — it always sends. There is no delete —
|
|
1749
|
+
* `muted: false` is the un-mute. */
|
|
1750
|
+
set: (input: {
|
|
1751
|
+
user_id?: string;
|
|
1752
|
+
template_id?: "welcome" | "transactional";
|
|
1753
|
+
muted: boolean;
|
|
1754
|
+
}) => Promise<{
|
|
1755
|
+
user_id: string;
|
|
1756
|
+
template_id: string | null;
|
|
1757
|
+
muted: boolean;
|
|
1758
|
+
updated_at: string;
|
|
1759
|
+
}>;
|
|
1524
1760
|
};
|
|
1525
1761
|
/** Deliveries the retry budget could not save (the dashboard "Resend" surface). */
|
|
1526
1762
|
deadLetters: {
|
|
@@ -1618,11 +1854,15 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
1618
1854
|
};
|
|
1619
1855
|
/** The planner (config-architect): describe an app in plain English and get an
|
|
1620
1856
|
* ADVISORY vxil plan — which features to enable, a materialized `config_draft`,
|
|
1621
|
-
*
|
|
1622
|
-
*
|
|
1623
|
-
*
|
|
1857
|
+
* which truly-unique logic needs a tenant function, and an architecture brief
|
|
1858
|
+
* (identity, data, money path, background work, limits, and the older patterns
|
|
1859
|
+
* vxil replaces). It PROPOSES a plan, it never applies anything (review it,
|
|
1860
|
+
* then push the config). Pass `{ brief: false }` to skip the brief.
|
|
1861
|
+
* Needs `features:read`. (POST /v1/plan; https://vxil.com/docs/guide/10-agents-and-mcp) */
|
|
1624
1862
|
readonly planner: {
|
|
1625
|
-
create: (description: string, name?: string
|
|
1863
|
+
create: (description: string, name?: string, opts?: {
|
|
1864
|
+
brief?: boolean;
|
|
1865
|
+
}) => Promise<VxilPlan>;
|
|
1626
1866
|
};
|
|
1627
1867
|
readonly audit: {
|
|
1628
1868
|
list: (q?: {
|
|
@@ -1832,7 +2072,7 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
1832
2072
|
delete: (ruleId: string) => Promise<void>;
|
|
1833
2073
|
};
|
|
1834
2074
|
};
|
|
1835
|
-
/** vxil-auth. SCOPES (docs/
|
|
2075
|
+
/** vxil-auth. SCOPES (vxil.com/docs/guide/09-security-and-multitenancy): every flow here that
|
|
1836
2076
|
* obtains, renews, verifies or ends the caller's OWN session (signUp/signIn,
|
|
1837
2077
|
* magicLink, otp, anonymous, stepUp, oauth, sessions.refresh/revoke/verify,
|
|
1838
2078
|
* password reset) accepts the narrow `auth:signin` — the ONLY auth scope a
|
|
@@ -1859,11 +2099,27 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
1859
2099
|
session: AuthSession;
|
|
1860
2100
|
verified: boolean;
|
|
1861
2101
|
}>;
|
|
2102
|
+
/** Magic-link sign-in: vxil mails a single-use link to the address and the
|
|
2103
|
+
* click returns here with `?token=`. The request is always a 202 —
|
|
2104
|
+
* identical for a known and an unknown address (anti-enumeration). A
|
|
2105
|
+
* second request for the SAME address inside the per-identifier cooldown
|
|
2106
|
+
* (auth config `magicLink.resendCooldownSec`, default 60s, 0 disables) is
|
|
2107
|
+
* `429 magic_link_rate_limited` with a `Retry-After` header. */
|
|
1862
2108
|
magicLink: {
|
|
2109
|
+
/** `test_link` is present ONLY when the address matches auth config
|
|
2110
|
+
* `otp.testRecipients` (store-review / CI accounts): no mail is sent and
|
|
2111
|
+
* the full sign-in link comes back instead. `captcha_token` is required
|
|
2112
|
+
* when the tenant configured `security.captchaSecretRef`; `locale`
|
|
2113
|
+
* chooses the mail's language. */
|
|
1863
2114
|
request: (input: {
|
|
1864
2115
|
email: string;
|
|
1865
2116
|
redirect_url: string;
|
|
1866
|
-
|
|
2117
|
+
locale?: string;
|
|
2118
|
+
captcha_token?: string;
|
|
2119
|
+
}) => Promise<{
|
|
2120
|
+
sent: true;
|
|
2121
|
+
test_link?: string;
|
|
2122
|
+
}>;
|
|
1867
2123
|
verify: (token: string) => Promise<{
|
|
1868
2124
|
user_id: string;
|
|
1869
2125
|
session: AuthSession;
|
|
@@ -2174,7 +2430,7 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
2174
2430
|
create: (input: {
|
|
2175
2431
|
collection: string;
|
|
2176
2432
|
singular?: string;
|
|
2177
|
-
/** End-user owner-scoping (docs/
|
|
2433
|
+
/** End-user owner-scoping (https://vxil.com/docs/guide/09-security-and-multitenancy):
|
|
2178
2434
|
* names an existing `string` field that holds the owner id. When set,
|
|
2179
2435
|
* the cms worker auto-scopes owned reads/writes to the VERIFIED
|
|
2180
2436
|
* end-user principal (default-deny) in end-user mode; a no-op in
|
|
@@ -2252,6 +2508,35 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
2252
2508
|
* the owner_field are never exposed. `false` closes the lane (and purges the
|
|
2253
2509
|
* edge cache). Read the public side with `cmsPublicUrl` / `listCmsPublic`. */
|
|
2254
2510
|
setPublic: (collection: string, isPublic: boolean) => Promise<void>;
|
|
2511
|
+
/** Re-project the collection's index slots after an `index_slot` move
|
|
2512
|
+
* (cms.md §19). Slots are projected on WRITE only, so until this runs,
|
|
2513
|
+
* stored rows keep their OLD projection: the new slot is NULL and the
|
|
2514
|
+
* VACATED slot still holds the old field's values — range/sort on the
|
|
2515
|
+
* moved field returns the WRONG rows, not merely missing ones. ONE page
|
|
2516
|
+
* per call (≤2000 rows, default 500), resumable via `next_cursor`,
|
|
2517
|
+
* idempotent (a second full pass reports `updated: 0`), and never a
|
|
2518
|
+
* version bump / CDC event (slots are derived columns). Server-key only.
|
|
2519
|
+
* `field` narrows the write to that field's slot plus any unowned slot. */
|
|
2520
|
+
reindex: (collection: string, opts?: {
|
|
2521
|
+
field?: string;
|
|
2522
|
+
cursor?: string;
|
|
2523
|
+
limit?: number;
|
|
2524
|
+
}) => Promise<CmsReindexPage>;
|
|
2525
|
+
/** Drive `reindex` to completion, page by page. Returns the totals.
|
|
2526
|
+
* A page that reports `skipped > 0` lost rows to a concurrent writer, so
|
|
2527
|
+
* it is re-run ONCE before the loop advances (the write is idempotent and
|
|
2528
|
+
* the retry is bounded — a permanently hot collection never spins). The
|
|
2529
|
+
* residual `skipped` is returned, not swallowed. */
|
|
2530
|
+
reindexAll: (collection: string, opts?: {
|
|
2531
|
+
field?: string;
|
|
2532
|
+
limit?: number;
|
|
2533
|
+
onPage?: (p: CmsReindexPage) => void;
|
|
2534
|
+
}) => Promise<{
|
|
2535
|
+
scanned: number;
|
|
2536
|
+
updated: number;
|
|
2537
|
+
skipped: number;
|
|
2538
|
+
pages: number;
|
|
2539
|
+
}>;
|
|
2255
2540
|
/** Replace the collection's per-record ACTION list (cms.md §17) — the
|
|
2256
2541
|
* `{ actions }` fields-route meta-op; `[]` clears. Each action is ONE
|
|
2257
2542
|
* human-initiated step: the dashboard renders it as a button per record,
|
|
@@ -2273,7 +2558,12 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
2273
2558
|
version: number;
|
|
2274
2559
|
data: Record<string, unknown>;
|
|
2275
2560
|
}>;
|
|
2276
|
-
|
|
2561
|
+
/** `expand` inlines relation/file fields into `data` (cms.md §6.2): the
|
|
2562
|
+
* full item envelope, a `{ object_id, $ref: 'files' }` stub for a file,
|
|
2563
|
+
* the bare id on a cycle/depth cut, `null` for an invisible target. */
|
|
2564
|
+
get: (collection: string, itemId: string, opts?: {
|
|
2565
|
+
expand?: readonly string[] | string;
|
|
2566
|
+
}) => Promise<Record<string, unknown>>;
|
|
2277
2567
|
/**
|
|
2278
2568
|
* The bounded query DSL. filter ops: $eq $ne $gt $gte $lt $lte $in
|
|
2279
2569
|
* $contains $startsWith (LIKE-escaped; substring-searchable on s*-slotted
|
|
@@ -2289,6 +2579,10 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
2289
2579
|
sort?: string;
|
|
2290
2580
|
limit?: number;
|
|
2291
2581
|
cursor?: string;
|
|
2582
|
+
/** relation/file fields to inline (`$expand`) — see `get`. The server
|
|
2583
|
+
* refuses `$expand` together with `count=true` (422 invalid_query), so
|
|
2584
|
+
* use `count()` for the aggregate. */
|
|
2585
|
+
expand?: readonly string[] | string;
|
|
2292
2586
|
}) => Promise<{
|
|
2293
2587
|
items: Array<Record<string, unknown>>;
|
|
2294
2588
|
next_cursor: string | null;
|
|
@@ -2313,6 +2607,22 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
2313
2607
|
cascaded: number;
|
|
2314
2608
|
set_null: number;
|
|
2315
2609
|
}>;
|
|
2610
|
+
/** N-22 (cms.md §20): BOUNDED FILTERED delete — soft-delete up to `limit`
|
|
2611
|
+
* items (1–100, default 25) matching `filter`, newest id first. A filter
|
|
2612
|
+
* is REQUIRED (an unselected sweep is refused 422). Each matched row runs
|
|
2613
|
+
* the SAME single-item delete path (restrict refusal, cascade budget,
|
|
2614
|
+
* unique-claim freeing, CDC), so this is N deletes in one round trip, not
|
|
2615
|
+
* a set-delete. `dryRun` reports `matched` and mutates nothing. Loop
|
|
2616
|
+
* while `deleted > 0`. A refusal mid-sweep (409 referenced / 422
|
|
2617
|
+
* cascade_too_large) stops the call — rows already deleted STAY deleted,
|
|
2618
|
+
* and the error message names how many. Soft-deleted rows are hard-purged
|
|
2619
|
+
* by the platform 30 days later (there is no per-tenant retention knob). */
|
|
2620
|
+
deleteMany: (collection: string, q: {
|
|
2621
|
+
filter: Record<string, unknown>;
|
|
2622
|
+
limit?: number;
|
|
2623
|
+
cursor?: string;
|
|
2624
|
+
dryRun?: boolean;
|
|
2625
|
+
}) => Promise<CmsBulkDeleteResult>;
|
|
2316
2626
|
publish: (collection: string, itemId: string) => Promise<Record<string, unknown>>;
|
|
2317
2627
|
/** P1-12 (cms.md §11.1): the BOUNDED reverse read — which live items
|
|
2318
2628
|
* reference this one, through which relation field. Owner-scoped in
|
|
@@ -2474,7 +2784,7 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
2474
2784
|
* only the envelope: membership, read-cursors, mute, and a directed block
|
|
2475
2785
|
* list; the messages themselves are `comments` rows on the
|
|
2476
2786
|
* `dm:<conversation_id>` topic. Gated by its own `dm` config, independent of
|
|
2477
|
-
* comments. See docs/
|
|
2787
|
+
* comments. See vxil.com/docs/guide/06-feature-catalog.
|
|
2478
2788
|
*/
|
|
2479
2789
|
readonly dm: {
|
|
2480
2790
|
/** Read the resolved DM config (defaults merged with the stored partial). */
|
|
@@ -3362,6 +3672,27 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
3362
3672
|
* over the template's stored schema. */
|
|
3363
3673
|
response_schema?: Record<string, unknown>;
|
|
3364
3674
|
images?: string[];
|
|
3675
|
+
/** DOCUMENT inputs (pdf / plain text), up to 8 refs, each a public https
|
|
3676
|
+
* URL, a `data:application/pdf;base64,…` (or `text/plain`) URL, or
|
|
3677
|
+
* `file:<object_id>`. Every ref is fetched + inlined server-side, so the
|
|
3678
|
+
* provider only ever sees base64. Needs a provider with the `documents`
|
|
3679
|
+
* capability (anthropic) — others answer 501 with a fixHint. */
|
|
3680
|
+
documents?: string[];
|
|
3681
|
+
/** PROMPT-CACHE breakpoints (anthropic; ignored elsewhere). `system`
|
|
3682
|
+
* caches the system prompt (tool definitions render before it and cache
|
|
3683
|
+
* with it); `messages` asks the provider to cache the transcript prefix;
|
|
3684
|
+
* `ttl` picks the 5-minute default or the 1-hour window. A cache READ
|
|
3685
|
+
* simply lowers the billed `input_tokens` — reported back as
|
|
3686
|
+
* `usage.cache_read_input_tokens` / `usage.cache_creation_input_tokens`
|
|
3687
|
+
* when the provider sends them. The minimum cacheable prefix is
|
|
3688
|
+
* model-dependent (512-4096 tokens); a shorter prompt silently does not
|
|
3689
|
+
* cache. The response cache key ignores this field (it changes billing,
|
|
3690
|
+
* not output). */
|
|
3691
|
+
cache?: {
|
|
3692
|
+
system?: boolean;
|
|
3693
|
+
messages?: boolean;
|
|
3694
|
+
ttl?: "5m" | "1h";
|
|
3695
|
+
};
|
|
3365
3696
|
user_id?: string;
|
|
3366
3697
|
}) => Promise<AiGeneration>;
|
|
3367
3698
|
/** Streamed generation: returns the channel + connect token immediately; open
|
|
@@ -3418,6 +3749,27 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
3418
3749
|
* usage frame). */
|
|
3419
3750
|
response_schema?: Record<string, unknown>;
|
|
3420
3751
|
images?: string[];
|
|
3752
|
+
/** DOCUMENT inputs (pdf / plain text), up to 8 refs, each a public https
|
|
3753
|
+
* URL, a `data:application/pdf;base64,…` (or `text/plain`) URL, or
|
|
3754
|
+
* `file:<object_id>`. Every ref is fetched + inlined server-side, so the
|
|
3755
|
+
* provider only ever sees base64. Needs a provider with the `documents`
|
|
3756
|
+
* capability (anthropic) — others answer 501 with a fixHint. */
|
|
3757
|
+
documents?: string[];
|
|
3758
|
+
/** PROMPT-CACHE breakpoints (anthropic; ignored elsewhere). `system`
|
|
3759
|
+
* caches the system prompt (tool definitions render before it and cache
|
|
3760
|
+
* with it); `messages` asks the provider to cache the transcript prefix;
|
|
3761
|
+
* `ttl` picks the 5-minute default or the 1-hour window. A cache READ
|
|
3762
|
+
* simply lowers the billed `input_tokens` — reported back as
|
|
3763
|
+
* `usage.cache_read_input_tokens` / `usage.cache_creation_input_tokens`
|
|
3764
|
+
* when the provider sends them. The minimum cacheable prefix is
|
|
3765
|
+
* model-dependent (512-4096 tokens); a shorter prompt silently does not
|
|
3766
|
+
* cache. The response cache key ignores this field (it changes billing,
|
|
3767
|
+
* not output). */
|
|
3768
|
+
cache?: {
|
|
3769
|
+
system?: boolean;
|
|
3770
|
+
messages?: boolean;
|
|
3771
|
+
ttl?: "5m" | "1h";
|
|
3772
|
+
};
|
|
3421
3773
|
user_id?: string;
|
|
3422
3774
|
}) => Promise<AiJobHandle>;
|
|
3423
3775
|
/** Re-mint a fresh connect token for a LIVE stream (a generation that
|
|
@@ -3648,7 +4000,23 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
3648
4000
|
* `quota` inlines one quota, `creditType` inlines the same owner-bound
|
|
3649
4001
|
* balance `getBalance` returns — ONE call for a thin client's paywall.
|
|
3650
4002
|
* A non-2xx answer means UNKNOWN: render the last cached answer, never
|
|
3651
|
-
* free (payments.md §3a).
|
|
4003
|
+
* free (payments.md §3a).
|
|
4004
|
+
*
|
|
4005
|
+
* OVERLAPPING SUBSCRIPTIONS — what `until` means. The WINNER is the
|
|
4006
|
+
* entitled subscription with the highest `tierMap[tier].rank` (default 0);
|
|
4007
|
+
* within a rank, the one with the latest period end (an open-ended manual
|
|
4008
|
+
* grant counts as unbounded), ties broken by subscription id, so the answer
|
|
4009
|
+
* is stable on every read. `entitlements` are the UNION and `quotas` the
|
|
4010
|
+
* MAX across ALL entitled rows, so a cancelled add-on never strips an
|
|
4011
|
+
* entitlement another live row still grants. `until` is the WINNER's OWN
|
|
4012
|
+
* boundary — not `max()` across rows, and not "when all access ends": it
|
|
4013
|
+
* answers *when does the CURRENT tier stop being good*. It is `null` for an
|
|
4014
|
+
* open-ended manual grant and on the `free` baseline. Across ranks a higher
|
|
4015
|
+
* tier wins even when a lower one runs longer, so `until` can report the
|
|
4016
|
+
* higher tier's earlier end; access does not dip (a lapsed winner re-folds
|
|
4017
|
+
* on read and the lower tier takes over), but a client that CACHES `until`
|
|
4018
|
+
* will show an early expiry — re-read instead of trusting a cached value
|
|
4019
|
+
* past it. */
|
|
3652
4020
|
getEntitlements: (userId: string, opts?: {
|
|
3653
4021
|
quota?: string;
|
|
3654
4022
|
creditType?: string;
|
|
@@ -3800,6 +4168,21 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
3800
4168
|
* refund the full un-refunded remainder. Idempotency-Key REQUIRED — a retry
|
|
3801
4169
|
* replays the recorded refund (never a second provider refund); a refund can
|
|
3802
4170
|
* NEVER exceed the charge (422 refund_exceeds_charge).
|
|
4171
|
+
*
|
|
4172
|
+
* PADDLE: a PARTIAL refund is issued as a per-line-item adjustment on the
|
|
4173
|
+
* Paddle transaction and typically settles ASYNCHRONOUSLY — it comes back
|
|
4174
|
+
* `pending` until your Paddle account approves the adjustment, and the
|
|
4175
|
+
* approving webhook flips it to `succeeded`. A rejected adjustment flips it
|
|
4176
|
+
* to `failed`, releases the reserved amount back onto the charge and emits
|
|
4177
|
+
* `payments.refund.failed`. While a refund is `pending`, the credits the
|
|
4178
|
+
* charge bought are NOT yet reversed. Stripe and the mock settle inline.
|
|
4179
|
+
*
|
|
4180
|
+
* If the provider has nothing left to refund on the charge — e.g. a refund
|
|
4181
|
+
* was already issued in the provider's own dashboard, which vxil's running
|
|
4182
|
+
* total cannot see — the call returns 422 `refund_not_allocatable` with the
|
|
4183
|
+
* precise reason, and nothing is sent to the provider. It is deterministic:
|
|
4184
|
+
* retrying the same refund fails the same way. A provider call that actually
|
|
4185
|
+
* fails is still 502 `provider_error`.
|
|
3803
4186
|
*/
|
|
3804
4187
|
createRefund: (input: {
|
|
3805
4188
|
charge_id: string;
|
|
@@ -3896,7 +4279,11 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
3896
4279
|
tier?: string;
|
|
3897
4280
|
}) => Promise<PaymentsSimulationResult>;
|
|
3898
4281
|
/** The last report-only reconciliation sweep result for this project
|
|
3899
|
-
* (`run: null` before the first daily tick).
|
|
4282
|
+
* (`run: null` before the first daily tick). Finding kinds:
|
|
4283
|
+
* `balance_drift`, `entitlement_lag`, `null_tier`, `refund_pending`,
|
|
4284
|
+
* `stranded_webhook`, `terminal_entitled`, `webhook_secret_missing`.
|
|
4285
|
+
* SERVER-SIDE KEYS ONLY — a thin-client (`end_user_required`) key gets
|
|
4286
|
+
* 403 `server_only`; the sweep is a tenant-wide operator surface. */
|
|
3900
4287
|
reconcile: () => Promise<{
|
|
3901
4288
|
run: {
|
|
3902
4289
|
run_id: string;
|
|
@@ -3935,7 +4322,11 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
|
|
|
3935
4322
|
/** Provider webhook event log (payments.md §7 "Event log & replay"):
|
|
3936
4323
|
* operator visibility over every delivery — incl. persisted signature
|
|
3937
4324
|
* failures — plus an idempotent reprocess verb. Needs payments:read
|
|
3938
|
-
* (reprocess: payments:write).
|
|
4325
|
+
* (reprocess: payments:write).
|
|
4326
|
+
* SERVER-SIDE KEYS ONLY — a thin-client (`end_user_required`) key gets
|
|
4327
|
+
* 403 `server_only` on list, detail and reprocess: the event log is a
|
|
4328
|
+
* tenant-wide operator surface and the detail carries the provider
|
|
4329
|
+
* payload (other customers' emails, provider ids and amounts). */
|
|
3939
4330
|
webhookEvents: {
|
|
3940
4331
|
/** List deliveries newest-first (keyset-paginated; pass `cursor` from a
|
|
3941
4332
|
* prior page's next_cursor). List rows omit payload/raw_body. */
|
package/dist/index.js
CHANGED
|
@@ -48,6 +48,16 @@ export class VxilError extends Error {
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
const DEFAULT_BASE = 'https://api.vxil.com';
|
|
51
|
+
/** Normalize the `expand` option to the `$expand` query value: a comma-joined
|
|
52
|
+
* list (the server's spelling), or `undefined` when nothing is expanded so the
|
|
53
|
+
* emitted URL is BYTE-IDENTICAL to a call without it. `qs()` percent-encodes
|
|
54
|
+
* the `$` in the key, which `url.searchParams.get('$expand')` decodes. */
|
|
55
|
+
function expandParam(e) {
|
|
56
|
+
if (e === undefined)
|
|
57
|
+
return undefined;
|
|
58
|
+
const list = (typeof e === 'string' ? [e] : e).filter((x) => typeof x === 'string' && x.length > 0);
|
|
59
|
+
return list.length ? list.join(',') : undefined;
|
|
60
|
+
}
|
|
51
61
|
/** Resolve the API base. Precedence: explicit `baseUrl` option > the
|
|
52
62
|
* `VXIL_BASE_URL` env var (Node/server only — guarded via `globalThis`, so it's
|
|
53
63
|
* inert in browsers & Workers) > the production default. This lets an internal /
|
|
@@ -150,7 +160,7 @@ export class Vxil {
|
|
|
150
160
|
* client, mirroring how the edge threads the verified principal. The base
|
|
151
161
|
* URL, api key, fetch impl, version pins and the retry/timeout/hooks options
|
|
152
162
|
* are inherited unchanged; only the end-user token is (re)set. Pass a falsy token to get back a server-mode
|
|
153
|
-
* client (drops the header). See docs/
|
|
163
|
+
* client (drops the header). See https://vxil.com/docs/guide/09-security-and-multitenancy. */
|
|
154
164
|
asEndUser(endUserToken) {
|
|
155
165
|
return new Vxil({
|
|
156
166
|
apiKey: this.key,
|
|
@@ -195,13 +205,21 @@ export class Vxil {
|
|
|
195
205
|
...(opts?.guard ? { guard: opts.guard } : {}),
|
|
196
206
|
...(opts?.guards ? { guards: opts.guards } : {}),
|
|
197
207
|
}),
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
208
|
+
// `expand` rides through as `$expand` on the wire; the envelope's `.data`
|
|
209
|
+
// already CARRIES the expanded members (cms-v1 replaces the stored id
|
|
210
|
+
// inside `data`), so the unwrap is unchanged and the overloads above are
|
|
211
|
+
// purely a type-level narrowing.
|
|
212
|
+
// (the two `as` casts pick the OVERLOADED member type: one runtime
|
|
213
|
+
// implementation serves both the plain and the `expand` overload, which
|
|
214
|
+
// differ only in the result TYPE — the wire call is the same.)
|
|
215
|
+
get: ((itemId, opts) => this.cms.items.get(c, itemId, opts).then(unwrap)),
|
|
216
|
+
query: ((q) => this.cms.items.query(c, q)
|
|
217
|
+
.then((r) => ({ items: r.items.map(unwrap), next_cursor: r.next_cursor }))),
|
|
201
218
|
count: (filter) => this.cms.items.count(c, filter),
|
|
202
219
|
patch: (itemId, data, opts) => this.cms.items.patch(c, itemId, data, opts).then(unwrap),
|
|
203
220
|
inc: (itemId, incs, opts) => this.cms.items.inc(c, itemId, incs, opts).then(unwrap),
|
|
204
221
|
delete: (itemId, opts) => this.cms.items.delete(c, itemId, opts),
|
|
222
|
+
deleteMany: (q) => this.cms.items.deleteMany(c, q),
|
|
205
223
|
publish: (itemId) => this.cms.items.publish(c, itemId).then(unwrap),
|
|
206
224
|
aggregate: (q) => this.cms.items.aggregate(c, q),
|
|
207
225
|
rank: (q) => this.cms.items.rank(c, q),
|
|
@@ -344,6 +362,50 @@ export class Vxil {
|
|
|
344
362
|
remove: async (email) => {
|
|
345
363
|
await this.call('DELETE', `/v1/notifications/suppressions/${encodeURIComponent(email)}`);
|
|
346
364
|
},
|
|
365
|
+
/** Bounce/complaint AGGREGATE over a 1–90 day window (default 30; an
|
|
366
|
+
* out-of-range or non-numeric `days` clamps rather than erroring).
|
|
367
|
+
* `counts` always carries all four reasons with explicit zeros; `rate` is
|
|
368
|
+
* `counts.total / sent` to 4 dp, or null when nothing was sent. `sent`
|
|
369
|
+
* counts deliveries the provider ACCEPTED, not inbox-delivered mail —
|
|
370
|
+
* this is a suppressions-per-accepted-send ratio, not an RFC bounce rate.
|
|
371
|
+
* Operator surface: a verified end-user token gets 403. */
|
|
372
|
+
stats: async (q) => {
|
|
373
|
+
const s = qs({ days: q?.days || undefined });
|
|
374
|
+
return (await this.call('GET', `/v1/notifications/suppressions/stats${s}`)).data;
|
|
375
|
+
},
|
|
376
|
+
},
|
|
377
|
+
/** Per-end-user MUTES — the opt-out table every send channel checks before
|
|
378
|
+
* it delivers. Absence of a row means "not muted". A row with no
|
|
379
|
+
* `template_id` is the ALL-TEMPLATES row.
|
|
380
|
+
*
|
|
381
|
+
* MUTE WINS. Enforcement is `(template_id = <template> OR template_id IS
|
|
382
|
+
* NULL) AND muted`, so the all-templates row SHADOWS a per-template
|
|
383
|
+
* `muted: false` — to re-enable one template after a master mute, clear the
|
|
384
|
+
* all-templates row (`{ muted: false }`, no `template_id`) and mute the
|
|
385
|
+
* others individually. `get` returns a computed `effective` per row, which
|
|
386
|
+
* is the answer enforcement gives; render THAT, not the stored `muted`.
|
|
387
|
+
*
|
|
388
|
+
* AUTH MAIL IS NEVER MUTEABLE. `magic-link` and `otp-code` carry sign-in
|
|
389
|
+
* links and codes: `set` rejects them with a 422 and the all-templates row
|
|
390
|
+
* does not cover them, so an "unsubscribe from everything" click can never
|
|
391
|
+
* lock a user out of its own account. Muteable ids: `welcome`,
|
|
392
|
+
* `transactional`. (Password-reset mail rides `transactional`, which stays
|
|
393
|
+
* muteable — see the notifications feature doc §6c.)
|
|
394
|
+
*
|
|
395
|
+
* SCOPES: with an end-user token both calls are confined to the verified
|
|
396
|
+
* principal and take `notifications:read` (a browser key that can mute its
|
|
397
|
+
* own mail must not also be able to send mail). With a server key
|
|
398
|
+
* `user_id` is required and `set` takes `notifications:send`. */
|
|
399
|
+
preferences: {
|
|
400
|
+
get: async (q) => {
|
|
401
|
+
const s = qs({ user_id: q.user_id || undefined });
|
|
402
|
+
return (await this.call('GET', `/v1/notifications/preferences${s}`)).data;
|
|
403
|
+
},
|
|
404
|
+
/** Idempotent upsert. OMIT `template_id` for the all-templates row; pass
|
|
405
|
+
* a MUTEABLE id to mute just that template. Auth mail (`magic-link`,
|
|
406
|
+
* `otp-code`) is a 422 — it always sends. There is no delete —
|
|
407
|
+
* `muted: false` is the un-mute. */
|
|
408
|
+
set: async (input) => (await this.call('PUT', '/v1/notifications/preferences', input)).data,
|
|
347
409
|
},
|
|
348
410
|
/** Deliveries the retry budget could not save (the dashboard "Resend" surface). */
|
|
349
411
|
deadLetters: {
|
|
@@ -400,13 +462,18 @@ export class Vxil {
|
|
|
400
462
|
};
|
|
401
463
|
/** The planner (config-architect): describe an app in plain English and get an
|
|
402
464
|
* ADVISORY vxil plan — which features to enable, a materialized `config_draft`,
|
|
403
|
-
*
|
|
404
|
-
*
|
|
405
|
-
*
|
|
465
|
+
* which truly-unique logic needs a tenant function, and an architecture brief
|
|
466
|
+
* (identity, data, money path, background work, limits, and the older patterns
|
|
467
|
+
* vxil replaces). It PROPOSES a plan, it never applies anything (review it,
|
|
468
|
+
* then push the config). Pass `{ brief: false }` to skip the brief.
|
|
469
|
+
* Needs `features:read`. (POST /v1/plan; https://vxil.com/docs/guide/10-agents-and-mcp) */
|
|
406
470
|
planner = {
|
|
407
|
-
create: async (description, name) => (await this.call('POST', '/v1/plan', {
|
|
471
|
+
create: async (description, name, opts) => (await this.call('POST', '/v1/plan', {
|
|
408
472
|
description,
|
|
409
473
|
...(name ? { name } : {}),
|
|
474
|
+
// sent ONLY when opting out, so every shipped call site's request body
|
|
475
|
+
// stays byte-identical
|
|
476
|
+
...(opts?.brief === false ? { brief: false } : {}),
|
|
410
477
|
})).data,
|
|
411
478
|
};
|
|
412
479
|
audit = {
|
|
@@ -544,7 +611,7 @@ export class Vxil {
|
|
|
544
611
|
},
|
|
545
612
|
},
|
|
546
613
|
};
|
|
547
|
-
/** vxil-auth. SCOPES (docs/
|
|
614
|
+
/** vxil-auth. SCOPES (vxil.com/docs/guide/09-security-and-multitenancy): every flow here that
|
|
548
615
|
* obtains, renews, verifies or ends the caller's OWN session (signUp/signIn,
|
|
549
616
|
* magicLink, otp, anonymous, stepUp, oauth, sessions.refresh/revoke/verify,
|
|
550
617
|
* password reset) accepts the narrow `auth:signin` — the ONLY auth scope a
|
|
@@ -557,10 +624,19 @@ export class Vxil {
|
|
|
557
624
|
auth = {
|
|
558
625
|
signUp: async (input) => (await this.call('POST', '/v1/auth/sign-up', input)).data,
|
|
559
626
|
signIn: async (input) => (await this.call('POST', '/v1/auth/sign-in', input)).data,
|
|
627
|
+
/** Magic-link sign-in: vxil mails a single-use link to the address and the
|
|
628
|
+
* click returns here with `?token=`. The request is always a 202 —
|
|
629
|
+
* identical for a known and an unknown address (anti-enumeration). A
|
|
630
|
+
* second request for the SAME address inside the per-identifier cooldown
|
|
631
|
+
* (auth config `magicLink.resendCooldownSec`, default 60s, 0 disables) is
|
|
632
|
+
* `429 magic_link_rate_limited` with a `Retry-After` header. */
|
|
560
633
|
magicLink: {
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
634
|
+
/** `test_link` is present ONLY when the address matches auth config
|
|
635
|
+
* `otp.testRecipients` (store-review / CI accounts): no mail is sent and
|
|
636
|
+
* the full sign-in link comes back instead. `captcha_token` is required
|
|
637
|
+
* when the tenant configured `security.captchaSecretRef`; `locale`
|
|
638
|
+
* chooses the mail's language. */
|
|
639
|
+
request: async (input) => (await this.call('POST', '/v1/auth/magic-link/request', input)).data,
|
|
564
640
|
verify: async (token) => (await this.call('POST', '/v1/auth/magic-link/verify', { token })).data,
|
|
565
641
|
},
|
|
566
642
|
/** Email OTP sign-in: a 6-digit single-use code (distinct from magic-link).
|
|
@@ -748,6 +824,59 @@ export class Vxil {
|
|
|
748
824
|
setPublic: async (collection, isPublic) => {
|
|
749
825
|
await this.call('POST', `/v1/cms/collections/${encodeURIComponent(collection)}/fields`, { public: isPublic });
|
|
750
826
|
},
|
|
827
|
+
/** Re-project the collection's index slots after an `index_slot` move
|
|
828
|
+
* (cms.md §19). Slots are projected on WRITE only, so until this runs,
|
|
829
|
+
* stored rows keep their OLD projection: the new slot is NULL and the
|
|
830
|
+
* VACATED slot still holds the old field's values — range/sort on the
|
|
831
|
+
* moved field returns the WRONG rows, not merely missing ones. ONE page
|
|
832
|
+
* per call (≤2000 rows, default 500), resumable via `next_cursor`,
|
|
833
|
+
* idempotent (a second full pass reports `updated: 0`), and never a
|
|
834
|
+
* version bump / CDC event (slots are derived columns). Server-key only.
|
|
835
|
+
* `field` narrows the write to that field's slot plus any unowned slot. */
|
|
836
|
+
reindex: async (collection, opts) => (await this.call('POST', `/v1/cms/collections/${encodeURIComponent(collection)}/reindex`, {
|
|
837
|
+
...(opts?.field ? { field: opts.field } : {}),
|
|
838
|
+
...(opts?.cursor ? { cursor: opts.cursor } : {}),
|
|
839
|
+
...(opts?.limit ? { limit: opts.limit } : {}),
|
|
840
|
+
})).data,
|
|
841
|
+
/** Drive `reindex` to completion, page by page. Returns the totals.
|
|
842
|
+
* A page that reports `skipped > 0` lost rows to a concurrent writer, so
|
|
843
|
+
* it is re-run ONCE before the loop advances (the write is idempotent and
|
|
844
|
+
* the retry is bounded — a permanently hot collection never spins). The
|
|
845
|
+
* residual `skipped` is returned, not swallowed. */
|
|
846
|
+
reindexAll: async (collection, opts) => {
|
|
847
|
+
let cursor;
|
|
848
|
+
let scanned = 0;
|
|
849
|
+
let updated = 0;
|
|
850
|
+
let skipped = 0;
|
|
851
|
+
let pages = 0;
|
|
852
|
+
for (;;) {
|
|
853
|
+
let page = await this.cms.collections.reindex(collection, {
|
|
854
|
+
...(opts?.field ? { field: opts.field } : {}),
|
|
855
|
+
...(opts?.limit ? { limit: opts.limit } : {}),
|
|
856
|
+
...(cursor ? { cursor } : {}),
|
|
857
|
+
});
|
|
858
|
+
scanned += page.scanned;
|
|
859
|
+
updated += page.updated;
|
|
860
|
+
pages += 1;
|
|
861
|
+
opts?.onPage?.(page);
|
|
862
|
+
if ((page.skipped ?? 0) > 0) {
|
|
863
|
+
page = await this.cms.collections.reindex(collection, {
|
|
864
|
+
...(opts?.field ? { field: opts.field } : {}),
|
|
865
|
+
...(opts?.limit ? { limit: opts.limit } : {}),
|
|
866
|
+
...(cursor ? { cursor } : {}),
|
|
867
|
+
});
|
|
868
|
+
scanned += page.scanned;
|
|
869
|
+
updated += page.updated;
|
|
870
|
+
pages += 1;
|
|
871
|
+
opts?.onPage?.(page);
|
|
872
|
+
}
|
|
873
|
+
skipped += page.skipped ?? 0;
|
|
874
|
+
if (page.complete || !page.next_cursor)
|
|
875
|
+
break;
|
|
876
|
+
cursor = page.next_cursor;
|
|
877
|
+
}
|
|
878
|
+
return { scanned, updated, skipped, pages };
|
|
879
|
+
},
|
|
751
880
|
/** Replace the collection's per-record ACTION list (cms.md §17) — the
|
|
752
881
|
* `{ actions }` fields-route meta-op; `[]` clears. Each action is ONE
|
|
753
882
|
* human-initiated step: the dashboard renders it as a button per record,
|
|
@@ -760,7 +889,13 @@ export class Vxil {
|
|
|
760
889
|
/** `lock` serializes same-key writers (per-tenant advisory lock); `guard`
|
|
761
890
|
* is the declarative capacity/overlap invariant (requires lock) — cms.md §10. */
|
|
762
891
|
create: async (collection, input) => (await this.call('POST', `/v1/cms/items/${encodeURIComponent(collection)}`, input)).data,
|
|
763
|
-
|
|
892
|
+
/** `expand` inlines relation/file fields into `data` (cms.md §6.2): the
|
|
893
|
+
* full item envelope, a `{ object_id, $ref: 'files' }` stub for a file,
|
|
894
|
+
* the bare id on a cycle/depth cut, `null` for an invisible target. */
|
|
895
|
+
get: async (collection, itemId, opts) => {
|
|
896
|
+
const s = qs({ $expand: expandParam(opts?.expand) });
|
|
897
|
+
return (await this.call('GET', `/v1/cms/items/${encodeURIComponent(collection)}/${encodeURIComponent(itemId)}${s}`)).data;
|
|
898
|
+
},
|
|
764
899
|
/**
|
|
765
900
|
* The bounded query DSL. filter ops: $eq $ne $gt $gte $lt $lte $in
|
|
766
901
|
* $contains $startsWith (LIKE-escaped; substring-searchable on s*-slotted
|
|
@@ -777,6 +912,7 @@ export class Vxil {
|
|
|
777
912
|
sort: q?.sort || undefined,
|
|
778
913
|
limit: q?.limit || undefined,
|
|
779
914
|
cursor: q?.cursor || undefined,
|
|
915
|
+
$expand: expandParam(q?.expand),
|
|
780
916
|
});
|
|
781
917
|
return (await this.call('GET', `/v1/cms/items/${encodeURIComponent(collection)}${s}`)).data;
|
|
782
918
|
},
|
|
@@ -804,6 +940,22 @@ export class Vxil {
|
|
|
804
940
|
/** Returns the cascade tally (cms.md §11); `ifVersion` rides If-Match and
|
|
805
941
|
* a conflict aborts BEFORE any cascade side-effect. */
|
|
806
942
|
delete: async (collection, itemId, opts) => (await this.call('DELETE', `/v1/cms/items/${encodeURIComponent(collection)}/${encodeURIComponent(itemId)}`, undefined, opts?.ifVersion !== undefined ? { 'if-match': String(opts.ifVersion) } : {})).data,
|
|
943
|
+
/** N-22 (cms.md §20): BOUNDED FILTERED delete — soft-delete up to `limit`
|
|
944
|
+
* items (1–100, default 25) matching `filter`, newest id first. A filter
|
|
945
|
+
* is REQUIRED (an unselected sweep is refused 422). Each matched row runs
|
|
946
|
+
* the SAME single-item delete path (restrict refusal, cascade budget,
|
|
947
|
+
* unique-claim freeing, CDC), so this is N deletes in one round trip, not
|
|
948
|
+
* a set-delete. `dryRun` reports `matched` and mutates nothing. Loop
|
|
949
|
+
* while `deleted > 0`. A refusal mid-sweep (409 referenced / 422
|
|
950
|
+
* cascade_too_large) stops the call — rows already deleted STAY deleted,
|
|
951
|
+
* and the error message names how many. Soft-deleted rows are hard-purged
|
|
952
|
+
* by the platform 30 days later (there is no per-tenant retention knob). */
|
|
953
|
+
deleteMany: async (collection, q) => (await this.call('POST', `/v1/cms/items/${encodeURIComponent(collection)}/delete`, {
|
|
954
|
+
filter: q.filter,
|
|
955
|
+
...(q.limit !== undefined ? { limit: q.limit } : {}),
|
|
956
|
+
...(q.cursor ? { cursor: q.cursor } : {}),
|
|
957
|
+
...(q.dryRun !== undefined ? { dry_run: q.dryRun } : {}),
|
|
958
|
+
})).data,
|
|
807
959
|
publish: async (collection, itemId) => (await this.call('POST', `/v1/cms/items/${encodeURIComponent(collection)}/${encodeURIComponent(itemId)}/publish`)).data,
|
|
808
960
|
/** P1-12 (cms.md §11.1): the BOUNDED reverse read — which live items
|
|
809
961
|
* reference this one, through which relation field. Owner-scoped in
|
|
@@ -880,7 +1032,7 @@ export class Vxil {
|
|
|
880
1032
|
* only the envelope: membership, read-cursors, mute, and a directed block
|
|
881
1033
|
* list; the messages themselves are `comments` rows on the
|
|
882
1034
|
* `dm:<conversation_id>` topic. Gated by its own `dm` config, independent of
|
|
883
|
-
* comments. See docs/
|
|
1035
|
+
* comments. See vxil.com/docs/guide/06-feature-catalog.
|
|
884
1036
|
*/
|
|
885
1037
|
dm = {
|
|
886
1038
|
/** Read the resolved DM config (defaults merged with the stored partial). */
|
|
@@ -1455,7 +1607,23 @@ export class Vxil {
|
|
|
1455
1607
|
* `quota` inlines one quota, `creditType` inlines the same owner-bound
|
|
1456
1608
|
* balance `getBalance` returns — ONE call for a thin client's paywall.
|
|
1457
1609
|
* A non-2xx answer means UNKNOWN: render the last cached answer, never
|
|
1458
|
-
* free (payments.md §3a).
|
|
1610
|
+
* free (payments.md §3a).
|
|
1611
|
+
*
|
|
1612
|
+
* OVERLAPPING SUBSCRIPTIONS — what `until` means. The WINNER is the
|
|
1613
|
+
* entitled subscription with the highest `tierMap[tier].rank` (default 0);
|
|
1614
|
+
* within a rank, the one with the latest period end (an open-ended manual
|
|
1615
|
+
* grant counts as unbounded), ties broken by subscription id, so the answer
|
|
1616
|
+
* is stable on every read. `entitlements` are the UNION and `quotas` the
|
|
1617
|
+
* MAX across ALL entitled rows, so a cancelled add-on never strips an
|
|
1618
|
+
* entitlement another live row still grants. `until` is the WINNER's OWN
|
|
1619
|
+
* boundary — not `max()` across rows, and not "when all access ends": it
|
|
1620
|
+
* answers *when does the CURRENT tier stop being good*. It is `null` for an
|
|
1621
|
+
* open-ended manual grant and on the `free` baseline. Across ranks a higher
|
|
1622
|
+
* tier wins even when a lower one runs longer, so `until` can report the
|
|
1623
|
+
* higher tier's earlier end; access does not dip (a lapsed winner re-folds
|
|
1624
|
+
* on read and the lower tier takes over), but a client that CACHES `until`
|
|
1625
|
+
* will show an early expiry — re-read instead of trusting a cached value
|
|
1626
|
+
* past it. */
|
|
1459
1627
|
getEntitlements: async (userId, opts) => {
|
|
1460
1628
|
const s = qs({
|
|
1461
1629
|
user_id: userId,
|
|
@@ -1527,6 +1695,21 @@ export class Vxil {
|
|
|
1527
1695
|
* refund the full un-refunded remainder. Idempotency-Key REQUIRED — a retry
|
|
1528
1696
|
* replays the recorded refund (never a second provider refund); a refund can
|
|
1529
1697
|
* NEVER exceed the charge (422 refund_exceeds_charge).
|
|
1698
|
+
*
|
|
1699
|
+
* PADDLE: a PARTIAL refund is issued as a per-line-item adjustment on the
|
|
1700
|
+
* Paddle transaction and typically settles ASYNCHRONOUSLY — it comes back
|
|
1701
|
+
* `pending` until your Paddle account approves the adjustment, and the
|
|
1702
|
+
* approving webhook flips it to `succeeded`. A rejected adjustment flips it
|
|
1703
|
+
* to `failed`, releases the reserved amount back onto the charge and emits
|
|
1704
|
+
* `payments.refund.failed`. While a refund is `pending`, the credits the
|
|
1705
|
+
* charge bought are NOT yet reversed. Stripe and the mock settle inline.
|
|
1706
|
+
*
|
|
1707
|
+
* If the provider has nothing left to refund on the charge — e.g. a refund
|
|
1708
|
+
* was already issued in the provider's own dashboard, which vxil's running
|
|
1709
|
+
* total cannot see — the call returns 422 `refund_not_allocatable` with the
|
|
1710
|
+
* precise reason, and nothing is sent to the provider. It is deterministic:
|
|
1711
|
+
* retrying the same refund fails the same way. A provider call that actually
|
|
1712
|
+
* fails is still 502 `provider_error`.
|
|
1530
1713
|
*/
|
|
1531
1714
|
createRefund: async (input, opts) => (await this.call('POST', '/v1/payments/refunds', input, { 'idempotency-key': opts.idempotencyKey })).data,
|
|
1532
1715
|
/** Where the user MANAGES their subscription, resolved by FUNDING SOURCE
|
|
@@ -1569,7 +1752,11 @@ export class Vxil {
|
|
|
1569
1752
|
* webhook path and judge it with the conformance oracle. */
|
|
1570
1753
|
simulate: async (input) => (await this.call('POST', '/v1/payments/simulate', input)).data,
|
|
1571
1754
|
/** The last report-only reconciliation sweep result for this project
|
|
1572
|
-
* (`run: null` before the first daily tick).
|
|
1755
|
+
* (`run: null` before the first daily tick). Finding kinds:
|
|
1756
|
+
* `balance_drift`, `entitlement_lag`, `null_tier`, `refund_pending`,
|
|
1757
|
+
* `stranded_webhook`, `terminal_entitled`, `webhook_secret_missing`.
|
|
1758
|
+
* SERVER-SIDE KEYS ONLY — a thin-client (`end_user_required`) key gets
|
|
1759
|
+
* 403 `server_only`; the sweep is a tenant-wide operator surface. */
|
|
1573
1760
|
reconcile: async () => (await this.call('GET', '/v1/payments/reconcile')).data,
|
|
1574
1761
|
/** List charges newest-first (the refund enabler — discover the charge_id).
|
|
1575
1762
|
* Filters: user_id, status, limit (clamped 1..100, default 50). */
|
|
@@ -1584,7 +1771,11 @@ export class Vxil {
|
|
|
1584
1771
|
/** Provider webhook event log (payments.md §7 "Event log & replay"):
|
|
1585
1772
|
* operator visibility over every delivery — incl. persisted signature
|
|
1586
1773
|
* failures — plus an idempotent reprocess verb. Needs payments:read
|
|
1587
|
-
* (reprocess: payments:write).
|
|
1774
|
+
* (reprocess: payments:write).
|
|
1775
|
+
* SERVER-SIDE KEYS ONLY — a thin-client (`end_user_required`) key gets
|
|
1776
|
+
* 403 `server_only` on list, detail and reprocess: the event log is a
|
|
1777
|
+
* tenant-wide operator surface and the detail carries the provider
|
|
1778
|
+
* payload (other customers' emails, provider ids and amounts). */
|
|
1588
1779
|
webhookEvents: {
|
|
1589
1780
|
/** List deliveries newest-first (keyset-paginated; pass `cursor` from a
|
|
1590
1781
|
* prior page's next_cursor). List rows omit payload/raw_body. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vxil/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.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).",
|
|
@@ -10,8 +10,7 @@
|
|
|
10
10
|
"backend",
|
|
11
11
|
"baas",
|
|
12
12
|
"sdk",
|
|
13
|
-
"typed-client"
|
|
14
|
-
"cloudflare-workers"
|
|
13
|
+
"typed-client"
|
|
15
14
|
],
|
|
16
15
|
"engines": {
|
|
17
16
|
"node": ">=18"
|