@productcraft/rally 0.0.3 → 0.0.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +161 -15
  2. package/package.json +2 -2
package/README.md CHANGED
@@ -1,50 +1,196 @@
1
1
  # @productcraft/rally
2
2
 
3
- Typed Node.js SDK for [ProductCraft Rally](https://productcraft.co) — waitlists, gated signups, and growth experiments.
3
+ Typed Node.js SDK for [ProductCraft Rally](https://productcraft.co) — waitlist management: public-form signups, variants for A/B/n landing pages, referrals, position + leaderboard, approval workflow with invite-to-app, signed outbound webhooks, CSV export.
4
4
 
5
5
  ```bash
6
6
  npm install @productcraft/rally
7
7
  ```
8
8
 
9
- Server-side only. The SDK ships a Platform API Key; never embed it in a browser bundle.
9
+ The waitlist-entries endpoint can be called unauthenticated from a marketing-site form. Every other endpoint (admin, approval, analytics, exports, webhooks) requires a PlatformUser cookie or a workspace-scoped PAK (`pcft_live_…`).
10
10
 
11
- ## Quick start
11
+ ## Quick start — accept a signup from a public form
12
12
 
13
13
  ```ts
14
14
  import { Rally } from "@productcraft/rally";
15
15
 
16
- const rally = new Rally({
17
- auth: { type: "apiKey", key: process.env.PCFT_KEY! },
18
- });
16
+ // No auth public surface
17
+ const rally = new Rally();
19
18
 
20
- // Accept a signup from a marketing site into a waitlist
21
19
  const { data, error } = await rally.client.POST(
22
- "/v1/waitlists/{workspaceSlug}/{waitlistSlug}/entries",
20
+ "/v1/waitlists/{workspace_slug}/{waitlist_slug}/entries",
23
21
  {
24
- params: { path: { workspaceSlug: "my-workspace", waitlistSlug: "early-access" } },
25
- body: { email: "alice@example.com" },
22
+ params: { path: { workspace_slug: "acme", waitlist_slug: "early-access" } },
23
+ body: {
24
+ email: "alice@example.com",
25
+ name: "Alice",
26
+ referrer: "twitter",
27
+ referral_code: "ada-123", // optional — bumps the referrer's position
28
+ metadata: { plan_interest: "pro" },
29
+ },
26
30
  },
27
31
  );
28
32
  ```
29
33
 
30
- The `client` is an [`openapi-fetch`](https://openapi-ts.dev/openapi-fetch/) instance bound to `https://api.rally.productcraft.co` and your auth credential. Every endpoint in the published OpenAPI spec is reachable through `client.GET`, `client.POST`, etc., with full path-param + body autocomplete.
34
+ The response includes the entry's id + assigned position. Round-trip the `id` into your "thanks" page so the visitor can share their own referral link.
31
35
 
32
- ## Public entry endpoint
36
+ If the waitlist has `settings.recaptcha_site_key` set, also pass a `recaptcha_token` from your client side.
33
37
 
34
- `POST /v1/waitlists/:workspaceSlug/:waitlistSlug/entries` is intentionally unauthenticated so a marketing site's `<form>` can POST signups directly. Every other Rally endpoint (admin, approval, exports) requires a PlatformUser cookie or a workspace-scoped PAK.
38
+ ## Quick start workspace-admin (authenticated)
39
+
40
+ ```ts
41
+ import { Rally } from "@productcraft/rally";
42
+
43
+ const rally = new Rally({
44
+ auth: { type: "apiKey", key: process.env.PCFT_KEY! },
45
+ });
46
+
47
+ // Create a waitlist — body uses `display_name`, not `name`.
48
+ const { data } = await rally.client.POST(
49
+ "/v1/workspaces/{workspace_id}/waitlists",
50
+ {
51
+ params: { path: { workspace_id: "<workspace-uuid>" } },
52
+ body: { display_name: "Early Access", slug: "early-access" },
53
+ },
54
+ );
55
+ ```
56
+
57
+ `{workspace_id}` is the workspace UUID returned by `@productcraft/platform-auth`'s introspect endpoint.
35
58
 
36
59
  ## Configuration
37
60
 
38
61
  ```ts
39
62
  new Rally({
63
+ // Optional: required for workspace-admin calls. Public submit works without auth.
40
64
  auth: { type: "apiKey", key: "pcft_live_..." }
41
65
  | { type: "bearer", token: "eyJ..." }
42
66
  | { type: "cookie", value: "auth_token=..." },
43
- baseUrl: "https://api.rally.example.test", // optional override
44
- fetch: customFetch, // optional
67
+ baseUrl: "https://api.rally.example.test", // optional override
68
+ fetch: customFetch, // optional
45
69
  });
46
70
  ```
47
71
 
72
+ ## Common operations
73
+
74
+ ### Public surface
75
+
76
+ ```ts
77
+ // Read the waitlist's public metadata (name, position-window, active variant, ...)
78
+ await rally.client.GET(
79
+ "/v1/waitlists/{workspace_slug}/{waitlist_slug}",
80
+ { params: { ... } },
81
+ );
82
+
83
+ // Read the public leaderboard (when enabled per-waitlist)
84
+ await rally.client.GET(
85
+ "/v1/waitlists/{workspace_slug}/{waitlist_slug}/leaderboard",
86
+ { params: { ... } },
87
+ );
88
+ ```
89
+
90
+ ### Entries (workspace-admin)
91
+
92
+ ```ts
93
+ // List entries
94
+ await rally.client.GET(
95
+ "/v1/workspaces/{workspace_id}/waitlists/{waitlist_id}/entries",
96
+ { params: { path: { workspace_id, waitlist_id }, query: { limit: 50 } } },
97
+ );
98
+
99
+ // Count
100
+ await rally.client.GET(
101
+ "/v1/workspaces/{workspace_id}/waitlists/{waitlist_id}/entries/count",
102
+ { ... },
103
+ );
104
+
105
+ // Approve / reject in bulk
106
+ await rally.client.POST(
107
+ "/v1/workspaces/{workspace_id}/waitlists/{waitlist_id}/entries/bulk",
108
+ { params: { ... }, body: { ids: [...], action: "approve" } },
109
+ );
110
+
111
+ // Send an invite (e.g. into a Heimdall app)
112
+ await rally.client.POST(
113
+ "/v1/workspaces/{workspace_id}/waitlists/{waitlist_id}/entries/{entry_id}/invite-to-app",
114
+ { params: { ... }, body: { /* app + role + invite_template */ } },
115
+ );
116
+
117
+ // Export to CSV
118
+ await rally.client.GET(
119
+ "/v1/workspaces/{workspace_id}/waitlists/{waitlist_id}/entries/export.csv",
120
+ { ... },
121
+ );
122
+ ```
123
+
124
+ ### Variants (A/B/n landing pages)
125
+
126
+ Variants split two ways: `kind: "ab"` for split-traffic A/B/n tests, or `kind: "locale"` for per-locale copy switching. Pick one; the body shape is `{ kind, slug, locale? }`.
127
+
128
+ ```ts
129
+ // A/B variant
130
+ await rally.client.POST(
131
+ "/v1/workspaces/{workspace_id}/waitlists/{waitlist_id}/variants",
132
+ {
133
+ params: { ... },
134
+ body: { kind: "ab", slug: "headline-b" },
135
+ },
136
+ );
137
+
138
+ // Locale variant
139
+ await rally.client.POST(
140
+ "/v1/workspaces/{workspace_id}/waitlists/{waitlist_id}/variants",
141
+ {
142
+ params: { ... },
143
+ body: { kind: "locale", slug: "pt-br", locale: "pt-BR" },
144
+ },
145
+ );
146
+ ```
147
+
148
+ Front-end picks up the active variant from `GET /v1/waitlists/:workspace_slug/:waitlist_slug` and round-trips its id back in the entry submission via the `variant_id` field — Rally computes per-variant conversion without a separate impressions table.
149
+
150
+ ### Analytics
151
+
152
+ ```ts
153
+ // Conversion + per-variant counts
154
+ await rally.client.GET(
155
+ "/v1/workspaces/{workspace_id}/waitlists/{waitlist_id}/analytics",
156
+ { ... },
157
+ );
158
+
159
+ // Timeseries — `since` is an ISO-8601 timestamp. There are no
160
+ // `bucket` / `lookback` query params.
161
+ await rally.client.GET(
162
+ "/v1/workspaces/{workspace_id}/waitlists/{waitlist_id}/analytics/timeline",
163
+ {
164
+ params: {
165
+ path: { ... },
166
+ query: { since: "2026-05-01T00:00:00Z" },
167
+ },
168
+ },
169
+ );
170
+ ```
171
+
172
+ ### Webhooks
173
+
174
+ ```ts
175
+ // Subscribe to entry.created / entry.approved / entry.rejected
176
+ await rally.client.POST(
177
+ "/v1/workspaces/{workspace_id}/webhooks",
178
+ { params: { ... }, body: { url: "https://yourapp.com/hooks/rally", events: ["entry.created"] } },
179
+ );
180
+
181
+ // Rotate the signing secret without disabling the webhook
182
+ await rally.client.POST(
183
+ "/v1/workspaces/{workspace_id}/webhooks/{id}/rotate-secret",
184
+ { ... },
185
+ );
186
+
187
+ // Replay a delivery from history
188
+ await rally.client.GET(
189
+ "/v1/workspaces/{workspace_id}/webhooks/{id}/deliveries",
190
+ { ... },
191
+ );
192
+ ```
193
+
48
194
  ## How this SDK is built
49
195
 
50
196
  Generated from the live OpenAPI spec at `https://api.rally.productcraft.co/docs-json` via [`openapi-typescript`](https://openapi-ts.dev/) + [`openapi-fetch`](https://openapi-ts.dev/openapi-fetch/). The nightly `spec-refresh` workflow opens a PR whenever the spec changes.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@productcraft/rally",
3
- "version": "0.0.3",
4
- "description": "Waitlists & growth experiments collect signups and gate access via ProductCraft Rally. Generated from the production OpenAPI spec.",
3
+ "version": "0.0.5",
4
+ "description": "Waitlist managementpublic-form signups, variants for A/B/n landing pages, referrals, position + leaderboard, approval workflow with invite-to-app, signed outbound webhooks, CSV export. Generated from the production OpenAPI spec.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.cjs",
7
7
  "module": "./dist/index.js",