@wefunder/sdk 0.1.0-beta.11 → 0.1.0-beta.12
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 +69 -20
- package/dist/index.cjs +99 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +91 -1
- package/dist/index.d.ts +91 -1
- package/dist/index.js +99 -0
- package/dist/index.js.map +1 -1
- package/examples_manifest.json +9 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -120,7 +120,9 @@ const investments = await wf.investments.list({ company_id: "co_example" });
|
|
|
120
120
|
const portfolio = await wf.portfolio.get();
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
-
Namespaces: `users`, `offerings`, `investments`, `portfolio`, `campaigns`, `syndicates`, `intents`, `attribution`, and `webhookEndpoints`.
|
|
123
|
+
Namespaces: `users`, `offerings`, `investments`, `portfolio`, `campaigns`, `syndicates`, `intents`, `attribution`, `installations`, and `webhookEndpoints`.
|
|
124
|
+
|
|
125
|
+
`wf.offerings.stats(id)` returns an offering's aggregate investment stats. `wf.installations` lets your app act as a company or syndicate: `eligibleTargets()`, `create()`, `mintToken()`, `list()`, `get()`, `revoke()`, and `installOrMintToken()`, which handles the API's 409 `already_installed` answer by minting a token for the existing install with the same scopes.
|
|
124
126
|
|
|
125
127
|
`wf.investments` is the Investment Delta API. `list()` without a cursor bootstraps; pass `updated_since` or the `meta.next_cursor` you saved from your last page to receive only records that changed since then. `next_cursor` is always present, even on the final page, so persist it after every sync.
|
|
126
128
|
|
|
@@ -191,25 +193,39 @@ await saveSecret(endpoint.attributes!.secret!);
|
|
|
191
193
|
Pass the raw request body, the request headers, and your secret to `constructEvent`. It throws `WebhookSignatureError` (with a `reason`) when a delivery is not authentic.
|
|
192
194
|
|
|
193
195
|
```ts
|
|
194
|
-
import {
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
196
|
+
import {
|
|
197
|
+
constructEvent,
|
|
198
|
+
dispatchWebhook,
|
|
199
|
+
WebhookSignatureError,
|
|
200
|
+
} from "@wefunder/sdk";
|
|
201
|
+
|
|
202
|
+
app.post(
|
|
203
|
+
"/webhooks/wefunder",
|
|
204
|
+
express.raw({ type: "*/*" }),
|
|
205
|
+
async (req, res) => {
|
|
206
|
+
let event;
|
|
207
|
+
try {
|
|
208
|
+
event = constructEvent(
|
|
209
|
+
req.body,
|
|
210
|
+
req.headers,
|
|
211
|
+
process.env.WEFUNDER_WEBHOOK_SECRET!,
|
|
212
|
+
);
|
|
213
|
+
} catch (err) {
|
|
214
|
+
if (err instanceof WebhookSignatureError)
|
|
215
|
+
return res.status(400).send(err.reason);
|
|
216
|
+
throw err;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
res.sendStatus(200); // acknowledge first, then do the work
|
|
220
|
+
|
|
221
|
+
await dispatchWebhook(event, {
|
|
222
|
+
"investment.executed": async (e) =>
|
|
223
|
+
recordFunding(e.data.id, e.data.amounts.committed),
|
|
224
|
+
"offering.opened": async (e) => announce(e.data.company.name),
|
|
225
|
+
default: (e) => console.log("unhandled", e.event),
|
|
226
|
+
});
|
|
227
|
+
},
|
|
228
|
+
);
|
|
213
229
|
```
|
|
214
230
|
|
|
215
231
|
`event` is a discriminated union, so narrowing on `event.event` types `event.data` for you. For fetch-style servers (Next.js route handlers, Hono, Cloudflare Workers), use `constructEventFromRequest(request, secret)` instead.
|
|
@@ -250,6 +266,39 @@ const members = await wf.unwrap(
|
|
|
250
266
|
|
|
251
267
|
Raw operations return `{ data, error, response }`. Passing the result to `wf.unwrap()` applies the same error handling used by the resource namespaces.
|
|
252
268
|
|
|
269
|
+
## Escape hatch: `wf.request` (any path)
|
|
270
|
+
|
|
271
|
+
For endpoints outside the generated surface entirely — preview-tier operations
|
|
272
|
+
(e.g. partner SPVs) or operations newer than your installed SDK version —
|
|
273
|
+
`wf.request()` calls any API path with the SDK's full envelope: bearer auth
|
|
274
|
+
(including refresh / client_credentials re-mint on 401), the pinned
|
|
275
|
+
`Wefunder-Version` header, the retry policy, and a typed `WefunderError` on
|
|
276
|
+
failure. It is untyped by design; preview endpoints can change at any time.
|
|
277
|
+
|
|
278
|
+
```ts
|
|
279
|
+
// GET with query params
|
|
280
|
+
const spvs = await wf.request("GET", "/partner/spvs", { query: { limit: 10 } });
|
|
281
|
+
|
|
282
|
+
// POST with a JSON body and an idempotency key
|
|
283
|
+
const session = await wf.request(
|
|
284
|
+
"POST",
|
|
285
|
+
`/partner/spvs/${spvId}/investment_sessions`,
|
|
286
|
+
{
|
|
287
|
+
body: {
|
|
288
|
+
investment_session: {
|
|
289
|
+
email: "alex@example.com",
|
|
290
|
+
allocation_cents: 500_000,
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
headers: { "Idempotency-Key": "invite-alex-1" },
|
|
294
|
+
},
|
|
295
|
+
);
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
The returned body is passed through as-is (no `{ data }` unwrapping — envelope
|
|
299
|
+
shapes vary across unshipped endpoints). When an operation graduates to the
|
|
300
|
+
generated surface, switch to `wf.raw.<opId>` (typed) or its namespace method.
|
|
301
|
+
|
|
253
302
|
## Development
|
|
254
303
|
|
|
255
304
|
```bash
|
package/dist/index.cjs
CHANGED
|
@@ -1947,6 +1947,32 @@ var Wefunder = class _Wefunder {
|
|
|
1947
1947
|
unwrap(p) {
|
|
1948
1948
|
return this.#unwrap(p);
|
|
1949
1949
|
}
|
|
1950
|
+
/**
|
|
1951
|
+
* Untyped escape hatch: call ANY API path with the SDK's full envelope —
|
|
1952
|
+
* bearer auth (incl. refresh / client_credentials re-mint on 401), the pinned
|
|
1953
|
+
* `Wefunder-Version` header, the retry policy, and a typed `WefunderError` on
|
|
1954
|
+
* failure. For endpoints outside the generated surface: preview-tier ops
|
|
1955
|
+
* (e.g. partner SPVs) or ops newer than this SDK build.
|
|
1956
|
+
*
|
|
1957
|
+
* `path` is relative to the client's `baseUrl` (e.g. `"/partner/spvs"`).
|
|
1958
|
+
* Returns the raw response body (no `{ data }` unwrapping — envelopes vary
|
|
1959
|
+
* across unshipped endpoints).
|
|
1960
|
+
*/
|
|
1961
|
+
async request(method, path, opts = {}) {
|
|
1962
|
+
return this.#unwrap(
|
|
1963
|
+
this.#client.request({
|
|
1964
|
+
method,
|
|
1965
|
+
url: path,
|
|
1966
|
+
// Same security descriptor the generated ops pass — this is what makes
|
|
1967
|
+
// the client attach (and on 401, refresh/re-mint) the bearer token.
|
|
1968
|
+
security: [{ scheme: "bearer", type: "http" }],
|
|
1969
|
+
query: opts.query,
|
|
1970
|
+
body: opts.body,
|
|
1971
|
+
// JSON by default when a body is present; caller headers win.
|
|
1972
|
+
headers: opts.body !== void 0 ? { "Content-Type": "application/json", ...opts.headers } : opts.headers
|
|
1973
|
+
})
|
|
1974
|
+
);
|
|
1975
|
+
}
|
|
1950
1976
|
// Generic page helper: forwards the endpoint's full query (cursor + documented
|
|
1951
1977
|
// params like `sort`), not just the cursor. (Stress-test finding B.)
|
|
1952
1978
|
#page(fn) {
|
|
@@ -1967,6 +1993,13 @@ var Wefunder = class _Wefunder {
|
|
|
1967
1993
|
client: this.#client,
|
|
1968
1994
|
path: { external_id: externalId }
|
|
1969
1995
|
})
|
|
1996
|
+
),
|
|
1997
|
+
/** Aggregate investment stats (count / committed / raised, by status) for one offering. */
|
|
1998
|
+
stats: (externalId) => this.#unwrapData(
|
|
1999
|
+
getOfferingStats({
|
|
2000
|
+
client: this.#client,
|
|
2001
|
+
path: { offering_id: externalId }
|
|
2002
|
+
})
|
|
1970
2003
|
)
|
|
1971
2004
|
};
|
|
1972
2005
|
/**
|
|
@@ -2037,6 +2070,72 @@ var Wefunder = class _Wefunder {
|
|
|
2037
2070
|
getAttributionMe({ client: this.#client })
|
|
2038
2071
|
)
|
|
2039
2072
|
};
|
|
2073
|
+
/**
|
|
2074
|
+
* Installations (`/installations`, scopes `read:installations` / `write:installations`;
|
|
2075
|
+
* write does not imply read). An install lets your app act AS a company or syndicate:
|
|
2076
|
+
* `create` and `mintToken` return a company-owned token (shown once, no expiry, no
|
|
2077
|
+
* refresh) — build a second client with it. Installing is also what makes a company
|
|
2078
|
+
* or syndicate an audience for your webhooks.
|
|
2079
|
+
*/
|
|
2080
|
+
installations = {
|
|
2081
|
+
/** Companies / syndicates the token's user may install your app on (empty for an investor). */
|
|
2082
|
+
eligibleTargets: (query) => this.#unwrapData(
|
|
2083
|
+
listEligibleInstallTargets({ client: this.#client, query })
|
|
2084
|
+
).then((rows) => rows ?? []),
|
|
2085
|
+
/** Every install of your app (`meta.count`). */
|
|
2086
|
+
list: () => this.#unwrap(
|
|
2087
|
+
listInstallations({ client: this.#client })
|
|
2088
|
+
),
|
|
2089
|
+
get: (id) => this.#unwrapData(
|
|
2090
|
+
getInstallation({
|
|
2091
|
+
client: this.#client,
|
|
2092
|
+
path: { external_id: id }
|
|
2093
|
+
})
|
|
2094
|
+
),
|
|
2095
|
+
/**
|
|
2096
|
+
* Install on a target. Store `token.access_token` — it is never shown again. If the
|
|
2097
|
+
* app is already installed there the API answers 409 `already_installed` with
|
|
2098
|
+
* `details.installation` = the existing id; mint a token for that instead
|
|
2099
|
+
* (see `installOrMintToken`).
|
|
2100
|
+
*/
|
|
2101
|
+
create: (input) => this.#unwrap(
|
|
2102
|
+
createInstallation({ client: this.#client, body: input })
|
|
2103
|
+
),
|
|
2104
|
+
/**
|
|
2105
|
+
* A fresh company-owned token for an existing install. `scopes` narrows within the
|
|
2106
|
+
* install's ceiling; omit it for the ceiling, and note an explicit `[]` grants nothing.
|
|
2107
|
+
*/
|
|
2108
|
+
mintToken: (id, scopes) => this.#unwrap(
|
|
2109
|
+
createInstallationToken({
|
|
2110
|
+
client: this.#client,
|
|
2111
|
+
path: { external_id: id },
|
|
2112
|
+
body: scopes ? { scopes } : void 0
|
|
2113
|
+
})
|
|
2114
|
+
),
|
|
2115
|
+
/**
|
|
2116
|
+
* `create`, falling back to `mintToken` for the existing install on 409
|
|
2117
|
+
* `already_installed`. The mint re-requests `input.scopes` so a retry never widens the
|
|
2118
|
+
* grant. Any other error (revoked install, missing scope) still throws.
|
|
2119
|
+
*/
|
|
2120
|
+
installOrMintToken: async (input) => {
|
|
2121
|
+
try {
|
|
2122
|
+
return await this.installations.create(input);
|
|
2123
|
+
} catch (err) {
|
|
2124
|
+
if (!(err instanceof WefunderError) || err.type !== "already_installed")
|
|
2125
|
+
throw err;
|
|
2126
|
+
const existingId = err.details?.installation;
|
|
2127
|
+
if (!existingId) throw err;
|
|
2128
|
+
return this.installations.mintToken(existingId, input.scopes);
|
|
2129
|
+
}
|
|
2130
|
+
},
|
|
2131
|
+
/** Revoke an install: its tokens stop working at once. Returns the install, now `revoked`. */
|
|
2132
|
+
revoke: (id) => this.#unwrapData(
|
|
2133
|
+
revokeInstallation({
|
|
2134
|
+
client: this.#client,
|
|
2135
|
+
path: { external_id: id }
|
|
2136
|
+
})
|
|
2137
|
+
)
|
|
2138
|
+
};
|
|
2040
2139
|
/**
|
|
2041
2140
|
* Webhook endpoints (`/webhook_endpoints`, scopes `read:webhooks` / `write:webhooks`).
|
|
2042
2141
|
* Endpoints belong to your application and are managed through the LIVE API —
|