@vxil/sdk 0.1.1 → 0.2.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 CHANGED
@@ -19,7 +19,7 @@ const items = await vx.from('tasks').list({ status: 'open' });
19
19
  - Zero dependencies; works in Node ≥18, browsers, and edge runtimes.
20
20
  - Defaults to `https://api.vxil.com`; pass `baseUrl` to target another environment.
21
21
  - Every feature the tenant has enabled is available as a typed namespace; generate a
22
- project-exact client with `npx vxil gen`.
22
+ project-exact client with `npx @vxil/cli gen`.
23
23
 
24
24
  Docs: [vxil.com](https://vxil.com) · Dashboard: [vxil.com/dashboard](https://vxil.com/dashboard) · Terms: [vxil.com/terms](https://vxil.com/terms)
25
25
 
package/dist/index.d.ts CHANGED
@@ -17,7 +17,7 @@ export interface VxilOptions {
17
17
  /** An end-user's vxil-`auth` **session token** (the ES256 JWT `auth` mints).
18
18
  * When set, every request additionally carries it as the `X-Vxil-End-User`
19
19
  * header — the edge verifies it and threads a **verified `end_user_id`** into
20
- * the inner JWT, putting the request in **end-user mode** (owner-scoped reads/
20
+ * the request, putting it in **end-user mode** (owner-scoped reads/
21
21
  * writes on owned collections; default-safe). This is the thin-client shape:
22
22
  * a mobile/SPA holds a public/thin-client key + the signed-in user's session
23
23
  * token and calls the edge directly, safely scoped to that user. Absent ⇒
@@ -84,6 +84,53 @@ export interface FeatureConfig {
84
84
  version: number;
85
85
  manifest: Record<string, unknown>;
86
86
  }
87
+ /** An advisory plan from the planner (POST /v1/plan) — a proposal, not applied. */
88
+ export interface VxilPlan {
89
+ app: {
90
+ name: string;
91
+ slug: string;
92
+ summary: string;
93
+ prompt: string;
94
+ };
95
+ /** features to enable, each with the capability that pulled it in. */
96
+ features: Array<{
97
+ feature: string;
98
+ why: string;
99
+ }>;
100
+ /** the raw features[] list for a quickstart body. */
101
+ enable: string[];
102
+ /** feature → materialized config manifest. */
103
+ config: Record<string, unknown>;
104
+ /** truly-unique server logic that needs a tenant function. */
105
+ functions: Array<{
106
+ why_unique: string;
107
+ }>;
108
+ /** cms collection drafts (AI tier; [] on the deterministic floor). */
109
+ collections: Array<{
110
+ name: string;
111
+ public?: boolean;
112
+ ownerField?: string;
113
+ fields: Array<{
114
+ name: string;
115
+ type: string;
116
+ required?: boolean;
117
+ unique?: boolean;
118
+ indexSlot?: string;
119
+ relationTo?: string;
120
+ onDelete?: string;
121
+ }>;
122
+ }>;
123
+ /** deny-clamped deploy-key scope union (informational). */
124
+ scopes: string[];
125
+ /** 1 = fully declarative; < 1 = some logic needs functions. */
126
+ thinness: number;
127
+ notes: string[];
128
+ /** a ready-to-review vxil.config.ts draft. */
129
+ config_draft: string;
130
+ rationale: string;
131
+ /** which brain produced the plan. */
132
+ source: 'ai' | 'deterministic';
133
+ }
87
134
  export interface DeadLetter {
88
135
  delivery_id: string;
89
136
  template_id: string;
@@ -164,7 +211,7 @@ export interface PaymentsWebhookEvent {
164
211
  processed_at: string | null;
165
212
  reprocess_count: number;
166
213
  reprocessed_at: string | null;
167
- /** detail-only: the verified provider payload (jsonb; '{}' for failed rows). */
214
+ /** detail-only: the verified provider payload (JSON; '{}' for failed rows). */
168
215
  payload?: unknown;
169
216
  /** detail-only: the raw body of a sig_failed/parse_failed delivery (≤64KB). */
170
217
  raw_body?: string | null;
@@ -315,7 +362,7 @@ export interface AiStreamHandle {
315
362
  connect_path?: string;
316
363
  resume_path: string;
317
364
  }
318
- /** Frames delivered over the ai:<generation_id> realtime channel and the
365
+ /** Frames delivered over the ai-<generation_id> realtime channel and the
319
366
  * ?since= replay buffer (the WS envelope is { event, data } — `event` is the
320
367
  * frame type; replayed frames carry it as `type`). `title` arrives early when
321
368
  * the stream was requested with title/title_template; only the terminal usage
@@ -670,6 +717,53 @@ export interface DmConfigState {
670
717
  version: number;
671
718
  configured: boolean;
672
719
  }
720
+ /** The safe query subset the keyless cms public lane admits (roadmap §4.4): the
721
+ * same bounded filter/sort/limit/cursor as the authored list, minus anything
722
+ * owner- or lifecycle-scoped (the lane FORCES status='published'). `filter` is a
723
+ * JSON object, serialized to the wire `filter=` param. */
724
+ export interface CmsPublicQuery {
725
+ filter?: Record<string, unknown>;
726
+ sort?: string;
727
+ /** capped at 100 by the public lane regardless of the value sent. */
728
+ limit?: number;
729
+ cursor?: string;
730
+ }
731
+ /** One row on the keyless public lane: the item id + its authored data + the
732
+ * public lifecycle timestamps. NO internal/system columns and NEVER the
733
+ * owner_field (stripped server-side). */
734
+ export interface CmsPublicRow {
735
+ item_id: string;
736
+ data: Record<string, unknown>;
737
+ created_at: string;
738
+ updated_at: string;
739
+ published_at: string | null;
740
+ }
741
+ /** Build the anonymous, KEYLESS public-delivery URL for a collection's PUBLISHED
742
+ * items (roadmap §4.4) — `{base}/v1/cms/public/:tenantId/:collection[?…]`. This is
743
+ * the reader path a public blog/storefront/docs site hits with NO api key: the
744
+ * edge mints a restricted read-only inner token bound to the URL tenant, forces
745
+ * `status='published'`, and strips the collection's owner_field. PURE (no
746
+ * network) — pass the result to `fetch`, an `<img>`/link, or `listCmsPublic`.
747
+ * `baseUrl` defaults to `VXIL_BASE_URL` (Node) or the production edge, exactly
748
+ * like the client constructor. */
749
+ export declare function cmsPublicUrl(tenantId: string, collection: string, query?: CmsPublicQuery, opts?: {
750
+ baseUrl?: string;
751
+ }): string;
752
+ /** Fetch a page of a collection's PUBLISHED items over the KEYLESS public lane
753
+ * (roadmap §4.4) — NO api key, no `Vxil` client, no auth of any kind. This is the
754
+ * anonymous reader path (a blog/storefront/docs front-end). Returns the same
755
+ * `{ items, next_cursor }` envelope the authed list does, but rows are stripped
756
+ * to the public surface (`CmsPublicRow`) — drafts and the owner_field are never
757
+ * present. A non-2xx throws `VxilError` carrying the envelope, as everywhere else.
758
+ * The collection must be marked public (`collections.setPublic(collection, true)`
759
+ * or `{ public: true }` on create) or the lane 404s (no not-public oracle). */
760
+ export declare function listCmsPublic(tenantId: string, collection: string, query?: CmsPublicQuery, opts?: {
761
+ baseUrl?: string;
762
+ fetch?: typeof fetch;
763
+ }): Promise<{
764
+ items: CmsPublicRow[];
765
+ next_cursor: string | null;
766
+ }>;
673
767
  /** The structural shape a `vxil gen`-generated `VxilSchema` satisfies. The base
674
768
  * `Vxil` class is generic over it (`new Vxil<VxilSchema>(...)`), exactly the
675
769
  * `createClient<Database>()` move — types are layered on; the runtime is
@@ -1111,6 +1205,14 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1111
1205
  };
1112
1206
  }>;
1113
1207
  };
1208
+ /** The planner (config-architect): describe an app in plain English and get an
1209
+ * ADVISORY vxil plan — which features to enable, a materialized `config_draft`,
1210
+ * and which truly-unique logic needs a tenant function. Deterministic + pure;
1211
+ * it PROPOSES a plan, it never applies anything (review it, then push the
1212
+ * config). Needs `features:read`. (POST /v1/plan; docs/planner-feature-design.md) */
1213
+ readonly planner: {
1214
+ create: (description: string, name?: string) => Promise<VxilPlan>;
1215
+ };
1114
1216
  readonly audit: {
1115
1217
  list: (q?: {
1116
1218
  since?: string;
@@ -1400,7 +1502,7 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1400
1502
  };
1401
1503
  /** End-user administration (server/function surface, auth:write). */
1402
1504
  users: {
1403
- /** GDPR PII erasure: anonymize the end-user's auth + tenant_users record
1505
+ /** GDPR PII erasure: anonymize the end-user's auth + end-user identity record
1404
1506
  * (email → tombstone, password/name/avatar cleared) and revoke every
1405
1507
  * live session. Idempotent — a second call on an already-erased user is a
1406
1508
  * no-op. Meant to be called from a "delete my account" server route or
@@ -1568,6 +1670,12 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1568
1670
  * end-user principal (default-deny) in end-user mode; a no-op in
1569
1671
  * server-caller mode. Omit for shared/reference collections. */
1570
1672
  owner_field?: string;
1673
+ /** Public delivery (roadmap §4.4): when true, this collection's PUBLISHED
1674
+ * items become KEYLESS-readable through the anonymous public lane —
1675
+ * `GET {base}/v1/cms/public/:tenantId/:collection` with NO api key. Drafts
1676
+ * and the owner_field are never exposed. Optional; defaults false. Use the
1677
+ * top-level `cmsPublicUrl` / `listCmsPublic` helpers for the reader side. */
1678
+ public?: boolean;
1571
1679
  fields?: Array<{
1572
1680
  field: string;
1573
1681
  type: "string" | "text" | "int" | "float" | "bool" | "datetime" | "json" | "relation" | "file";
@@ -1591,6 +1699,7 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1591
1699
  collection: string;
1592
1700
  fields: number;
1593
1701
  owner_field?: string;
1702
+ public?: boolean;
1594
1703
  }>;
1595
1704
  list: () => Promise<Array<{
1596
1705
  collection: string;
@@ -1602,6 +1711,12 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1602
1711
  /** Set (or clear, with `null`) the collection's end-user owner-scope flag
1603
1712
  * (design §5.1). Names an existing `string` field that holds the owner id. */
1604
1713
  setOwnerField: (collection: string, ownerField: string | null) => Promise<void>;
1714
+ /** Toggle the collection's PUBLIC-delivery flag (roadmap §4.4). When `true`,
1715
+ * its PUBLISHED items become KEYLESS-readable via the anonymous public lane
1716
+ * (`GET {base}/v1/cms/public/:tenantId/:collection` — no api key); drafts and
1717
+ * the owner_field are never exposed. `false` closes the lane (and purges the
1718
+ * edge cache). Read the public side with `cmsPublicUrl` / `listCmsPublic`. */
1719
+ setPublic: (collection: string, isPublic: boolean) => Promise<void>;
1605
1720
  };
1606
1721
  items: {
1607
1722
  /** `lock` serializes same-key writers (per-tenant advisory lock); `guard`
@@ -1621,11 +1736,11 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
1621
1736
  get: (collection: string, itemId: string) => Promise<Record<string, unknown>>;
1622
1737
  /**
1623
1738
  * The bounded query DSL. filter ops: $eq $ne $gt $gte $lt $lte $in
1624
- * $contains $startsWith (LIKE-escaped; trigram-indexed on s*-slotted
1625
- * fields) $arrayContains/$anyOf (json/relation array containment via the
1626
- * JSONB GIN); range/sort needs slot-indexed fields. This is the
1739
+ * $contains $startsWith (LIKE-escaped; substring-searchable on s*-slotted
1740
+ * fields) $arrayContains/$anyOf (json/relation array containment, indexed);
1741
+ * range/sort needs slot-indexed fields. This is the
1627
1742
  * DELIBERATELY-UNTYPED escape hatch: it accepts everything the server
1628
- * admits, including unslotted range ops (served by unindexed JSONB
1743
+ * admits, including unslotted range ops (served by unindexed
1629
1744
  * scans, bounded only by the statement timeout) that the generated
1630
1745
  * per-field `Filterable` unions on `vx.from(...).query` exclude.
1631
1746
  */
@@ -2270,7 +2385,7 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
2270
2385
  settings?: Record<string, unknown>;
2271
2386
  role?: string | null;
2272
2387
  }>;
2273
- /** Update a workspace's name / `settings` jsonb. */
2388
+ /** Update a workspace's name / `settings` JSON. */
2274
2389
  update: (orgId: string, patch: {
2275
2390
  name?: string;
2276
2391
  settings?: Record<string, unknown>;
@@ -2521,7 +2636,7 @@ export declare class Vxil<S extends VxilSchemaShape = VxilSchemaShape> {
2521
2636
  };
2522
2637
  /**
2523
2638
  * Managed hybrid search (the `vector-search` feature): store documents → chunk →
2524
- * embed → index, then retrieve top-k via hybrid (BM25 ∪ vector, RRF-fused),
2639
+ * embed → index, then retrieve top-k via hybrid (keyword ∪ vector, RRF-fused),
2525
2640
  * vector, or keyword. The embedder is config: 'mock' (deterministic default),
2526
2641
  * 'byov' (you supply vectors), or a BYO-key provider (openai/cohere).
2527
2642
  */
package/dist/index.js CHANGED
@@ -43,6 +43,57 @@ function resolveBase(explicit) {
43
43
  const envBase = g.process?.env?.VXIL_BASE_URL;
44
44
  return (explicit ?? envBase ?? DEFAULT_BASE).replace(/\/$/, '');
45
45
  }
46
+ /** Build the anonymous, KEYLESS public-delivery URL for a collection's PUBLISHED
47
+ * items (roadmap §4.4) — `{base}/v1/cms/public/:tenantId/:collection[?…]`. This is
48
+ * the reader path a public blog/storefront/docs site hits with NO api key: the
49
+ * edge mints a restricted read-only inner token bound to the URL tenant, forces
50
+ * `status='published'`, and strips the collection's owner_field. PURE (no
51
+ * network) — pass the result to `fetch`, an `<img>`/link, or `listCmsPublic`.
52
+ * `baseUrl` defaults to `VXIL_BASE_URL` (Node) or the production edge, exactly
53
+ * like the client constructor. */
54
+ export function cmsPublicUrl(tenantId, collection, query, opts) {
55
+ const base = resolveBase(opts?.baseUrl);
56
+ const path = `/v1/cms/public/${encodeURIComponent(tenantId)}/${encodeURIComponent(collection)}`;
57
+ const qs = new URLSearchParams();
58
+ if (query?.filter)
59
+ qs.set('filter', JSON.stringify(query.filter));
60
+ if (query?.sort)
61
+ qs.set('sort', query.sort);
62
+ if (query?.limit !== undefined)
63
+ qs.set('limit', String(query.limit));
64
+ if (query?.cursor)
65
+ qs.set('cursor', query.cursor);
66
+ const s = qs.toString();
67
+ return `${base}${path}${s ? `?${s}` : ''}`;
68
+ }
69
+ /** Fetch a page of a collection's PUBLISHED items over the KEYLESS public lane
70
+ * (roadmap §4.4) — NO api key, no `Vxil` client, no auth of any kind. This is the
71
+ * anonymous reader path (a blog/storefront/docs front-end). Returns the same
72
+ * `{ items, next_cursor }` envelope the authed list does, but rows are stripped
73
+ * to the public surface (`CmsPublicRow`) — drafts and the owner_field are never
74
+ * present. A non-2xx throws `VxilError` carrying the envelope, as everywhere else.
75
+ * The collection must be marked public (`collections.setPublic(collection, true)`
76
+ * or `{ public: true }` on create) or the lane 404s (no not-public oracle). */
77
+ export async function listCmsPublic(tenantId, collection, query, opts) {
78
+ const url = cmsPublicUrl(tenantId, collection, query, opts);
79
+ const fetchImpl = opts?.fetch ?? fetch;
80
+ const res = await fetchImpl(url, { method: 'GET' });
81
+ const text = await res.text();
82
+ let parsed = {};
83
+ if (text.length > 0) {
84
+ try {
85
+ parsed = JSON.parse(text);
86
+ }
87
+ catch {
88
+ throw new VxilError(res.status, `http_${res.status}`, text.slice(0, 200));
89
+ }
90
+ }
91
+ if (!res.ok || parsed.error) {
92
+ const e = parsed.error ?? { code: `http_${res.status}`, message: text.slice(0, 200) };
93
+ throw new VxilError(res.status, e.code, e.message, e.hint, e.fixUrl, parsed.meta?.request_id);
94
+ }
95
+ return parsed.data ?? { items: [], next_cursor: null };
96
+ }
46
97
  export class Vxil {
47
98
  base;
48
99
  key;
@@ -302,6 +353,17 @@ export class Vxil {
302
353
  * stable flat-array shorthand. */
303
354
  summary: async () => (await this.call('GET', '/v1/features')).data,
304
355
  };
356
+ /** The planner (config-architect): describe an app in plain English and get an
357
+ * ADVISORY vxil plan — which features to enable, a materialized `config_draft`,
358
+ * and which truly-unique logic needs a tenant function. Deterministic + pure;
359
+ * it PROPOSES a plan, it never applies anything (review it, then push the
360
+ * config). Needs `features:read`. (POST /v1/plan; docs/planner-feature-design.md) */
361
+ planner = {
362
+ create: async (description, name) => (await this.call('POST', '/v1/plan', {
363
+ description,
364
+ ...(name ? { name } : {}),
365
+ })).data,
366
+ };
305
367
  audit = {
306
368
  list: async (q) => {
307
369
  const qs = new URLSearchParams();
@@ -481,7 +543,7 @@ export class Vxil {
481
543
  },
482
544
  /** End-user administration (server/function surface, auth:write). */
483
545
  users: {
484
- /** GDPR PII erasure: anonymize the end-user's auth + tenant_users record
546
+ /** GDPR PII erasure: anonymize the end-user's auth + end-user identity record
485
547
  * (email → tombstone, password/name/avatar cleared) and revoke every
486
548
  * live session. Idempotent — a second call on an already-erased user is a
487
549
  * no-op. Meant to be called from a "delete my account" server route or
@@ -574,6 +636,14 @@ export class Vxil {
574
636
  setOwnerField: async (collection, ownerField) => {
575
637
  await this.call('POST', `/v1/cms/collections/${encodeURIComponent(collection)}/fields`, { owner_field: ownerField });
576
638
  },
639
+ /** Toggle the collection's PUBLIC-delivery flag (roadmap §4.4). When `true`,
640
+ * its PUBLISHED items become KEYLESS-readable via the anonymous public lane
641
+ * (`GET {base}/v1/cms/public/:tenantId/:collection` — no api key); drafts and
642
+ * the owner_field are never exposed. `false` closes the lane (and purges the
643
+ * edge cache). Read the public side with `cmsPublicUrl` / `listCmsPublic`. */
644
+ setPublic: async (collection, isPublic) => {
645
+ await this.call('POST', `/v1/cms/collections/${encodeURIComponent(collection)}/fields`, { public: isPublic });
646
+ },
577
647
  },
578
648
  items: {
579
649
  /** `lock` serializes same-key writers (per-tenant advisory lock); `guard`
@@ -582,11 +652,11 @@ export class Vxil {
582
652
  get: async (collection, itemId) => (await this.call('GET', `/v1/cms/items/${encodeURIComponent(collection)}/${encodeURIComponent(itemId)}`)).data,
583
653
  /**
584
654
  * The bounded query DSL. filter ops: $eq $ne $gt $gte $lt $lte $in
585
- * $contains $startsWith (LIKE-escaped; trigram-indexed on s*-slotted
586
- * fields) $arrayContains/$anyOf (json/relation array containment via the
587
- * JSONB GIN); range/sort needs slot-indexed fields. This is the
655
+ * $contains $startsWith (LIKE-escaped; substring-searchable on s*-slotted
656
+ * fields) $arrayContains/$anyOf (json/relation array containment, indexed);
657
+ * range/sort needs slot-indexed fields. This is the
588
658
  * DELIBERATELY-UNTYPED escape hatch: it accepts everything the server
589
- * admits, including unslotted range ops (served by unindexed JSONB
659
+ * admits, including unslotted range ops (served by unindexed
590
660
  * scans, bounded only by the statement timeout) that the generated
591
661
  * per-field `Filterable` unions on `vx.from(...).query` exclude.
592
662
  */
@@ -937,7 +1007,7 @@ export class Vxil {
937
1007
  sessionClaims: async (userId) => (await this.call('GET', `/v1/orgs/session-claims?user_id=${encodeURIComponent(userId)}`)).data,
938
1008
  /** Read one workspace (incl. `settings`). */
939
1009
  get: async (orgId) => (await this.call('GET', `/v1/orgs/${encodeURIComponent(orgId)}`)).data,
940
- /** Update a workspace's name / `settings` jsonb. */
1010
+ /** Update a workspace's name / `settings` JSON. */
941
1011
  update: async (orgId, patch) => (await this.call('PATCH', `/v1/orgs/${encodeURIComponent(orgId)}`, patch)).data,
942
1012
  members: {
943
1013
  add: async (orgId, userId, role) => {
@@ -1060,7 +1130,7 @@ export class Vxil {
1060
1130
  };
1061
1131
  /**
1062
1132
  * Managed hybrid search (the `vector-search` feature): store documents → chunk →
1063
- * embed → index, then retrieve top-k via hybrid (BM25 ∪ vector, RRF-fused),
1133
+ * embed → index, then retrieve top-k via hybrid (keyword ∪ vector, RRF-fused),
1064
1134
  * vector, or keyword. The embedder is config: 'mock' (deterministic default),
1065
1135
  * 'byov' (you supply vectors), or a BYO-key provider (openai/cohere).
1066
1136
  */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vxil/sdk",
3
- "version": "0.1.1",
3
+ "version": "0.2.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).",
@@ -28,11 +28,10 @@
28
28
  "files": [
29
29
  "dist"
30
30
  ],
31
- "scripts": {
32
- "build": "tsc -p tsconfig.build.json",
33
- "prepublishOnly": "pnpm run build"
34
- },
35
31
  "publishConfig": {
36
32
  "access": "public"
33
+ },
34
+ "scripts": {
35
+ "build": "tsc -p tsconfig.build.json"
37
36
  }
38
37
  }