@postact-js/sdk 0.1.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 ADDED
@@ -0,0 +1,52 @@
1
+ # @postact-js/sdk
2
+
3
+ Typed client for the [postact](https://postact.app) API. One POST per action, zero dependencies.
4
+
5
+ ```sh
6
+ npm i @postact-js/sdk
7
+ ```
8
+
9
+ ```ts
10
+ import { createClient } from "@postact-js/sdk";
11
+
12
+ // One client per project — create it once, at module scope.
13
+ export const postact = createClient<{
14
+ signup: { plan: string };
15
+ "contact-form": { message: string };
16
+ }>({
17
+ project: "acme", // your project slug
18
+ key: process.env.POSTACT_SK!, // sk_… server key
19
+ });
20
+
21
+ // action + payload are checked against the map above
22
+ const jobId = await postact.send("signup", { email: "ada@example.com" }, { plan: "pro" });
23
+ ```
24
+
25
+ Untyped is fine too — `createClient({ … })` accepts any action slug and payload.
26
+
27
+ ## Person
28
+
29
+ Every call needs a person with an email **or** at least one identifier:
30
+
31
+ ```ts
32
+ await postact.send("signup", {
33
+ email: "ada@example.com", // null clears it, omitted leaves it untouched
34
+ identifiers: { userId: 42 }, // your stable ids — used to merge, never duplicate
35
+ metadata: { plan: "pro" }, // stored on the person
36
+ });
37
+ ```
38
+
39
+ ## Browser
40
+
41
+ Use a `pk_…` key and add the origin to the action's allowlist in the dashboard. Never ship an
42
+ `sk_…` key to a browser — it can read and write everything in the project.
43
+
44
+ ```ts
45
+ const postact = createClient({ project: "acme", key: "pk_live_…" });
46
+ ```
47
+
48
+ ## Errors
49
+
50
+ Non-2xx throws a `PostactError` carrying the server's message and `status` (401 bad key, 404
51
+ unknown project/action, 429 rate limited, 400 invalid body). `send` resolves with a **job id** —
52
+ the API is async, so a resolved promise means "queued", not "the action's steps ran".
@@ -0,0 +1,34 @@
1
+ export type Identifiers = Record<string, string | number>;
2
+ export type Metadata = Record<string, string | number | boolean | null>;
3
+ /** Who the action is about. Needs an email or at least one identifier. */
4
+ export type Person = {
5
+ /** `null` clears the address, omitted leaves it untouched */
6
+ email?: string | null;
7
+ identifiers?: Identifiers;
8
+ metadata?: Metadata;
9
+ };
10
+ /** `{ "signup": { plan: string }, "contact-form": { message: string } }` */
11
+ export type ActionMap = Record<string, Record<string, unknown> | undefined>;
12
+ export type ClientOptions = {
13
+ /** project slug, e.g. `"acme"` */
14
+ project: string;
15
+ /** `sk_…` server key, or `pk_…` from a browser on an allowlisted origin */
16
+ key: string;
17
+ /** default `https://api.postact.app` */
18
+ baseUrl?: string;
19
+ /** override for tests / custom agents */
20
+ fetch?: typeof globalThis.fetch;
21
+ };
22
+ export declare class PostactError extends Error {
23
+ readonly status: number;
24
+ constructor(message: string, status: number);
25
+ }
26
+ export declare function createClient<A extends ActionMap = ActionMap>(options: ClientOptions): {
27
+ /**
28
+ * Fire one action. Resolves with the queued job id — the API is async (202 + job),
29
+ * so this returning does not mean the action's steps have run.
30
+ */
31
+ send<K extends keyof A & string>(action: K, person: Person, payload?: A[K]): Promise<string>;
32
+ };
33
+ export type PostactClient<A extends ActionMap = ActionMap> = ReturnType<typeof createClient<A>>;
34
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAKA,MAAM,MAAM,WAAW,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;AAC1D,MAAM,MAAM,QAAQ,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,GAAG,IAAI,CAAC,CAAC;AAExE,0EAA0E;AAC1E,MAAM,MAAM,MAAM,GAAG;IACnB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,QAAQ,CAAC,EAAE,QAAQ,CAAC;CACrB,CAAC;AAEF,4EAA4E;AAC5E,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,CAAC,CAAC;AAE5E,MAAM,MAAM,aAAa,GAAG;IAC1B,kCAAkC;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,2EAA2E;IAC3E,GAAG,EAAE,MAAM,CAAC;IACZ,wCAAwC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,yCAAyC;IACzC,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC,CAAC;AAEF,qBAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;gBACZ,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM;CAK5C;AAED,wBAAgB,YAAY,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS,EAAE,OAAO,EAAE,aAAa;IAOhF;;;OAGG;SACQ,CAAC,SAAS,MAAM,CAAC,GAAG,MAAM,UAC3B,CAAC,UACD,MAAM,YACJ,CAAC,CAAC,CAAC,CAAC,GACb,OAAO,CAAC,MAAM,CAAC;EAmBrB;AAED,MAAM,MAAM,aAAa,CAAC,CAAC,SAAS,SAAS,GAAG,SAAS,IAAI,UAAU,CAAC,OAAO,YAAY,CAAC,CAAC,CAAC,CAAC,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,38 @@
1
+ // Zero-dependency client for the postact one-POST API:
2
+ // POST {baseUrl}/p/<project>/a/<action> { person, payload }
3
+ // Typing comes from a caller-supplied action→payload map — no codegen, no generated schema to
4
+ // drift; the map *is* the contract. Works anywhere `fetch` exists (Node 18+, Bun, browsers, edge).
5
+ export class PostactError extends Error {
6
+ status;
7
+ constructor(message, status) {
8
+ super(message);
9
+ this.name = "PostactError";
10
+ this.status = status;
11
+ }
12
+ }
13
+ export function createClient(options) {
14
+ const base = (options.baseUrl ?? "https://api.postact.app").replace(/\/+$/, "");
15
+ const project = encodeURIComponent(options.project);
16
+ // bind: an unbound globalThis.fetch throws "Illegal invocation" in browsers
17
+ const call = options.fetch ?? globalThis.fetch.bind(globalThis);
18
+ return {
19
+ /**
20
+ * Fire one action. Resolves with the queued job id — the API is async (202 + job),
21
+ * so this returning does not mean the action's steps have run.
22
+ */
23
+ async send(action, person, payload) {
24
+ // The server enforces this too; checking here saves a round trip for the common typo.
25
+ if (person.email === undefined && !Object.keys(person.identifiers ?? {}).length)
26
+ throw new PostactError("person needs an email or at least one identifier", 400);
27
+ const res = await call(`${base}/p/${project}/a/${encodeURIComponent(action)}`, {
28
+ method: "POST",
29
+ headers: { "content-type": "application/json", authorization: `Bearer ${options.key}` },
30
+ body: JSON.stringify({ person, payload }),
31
+ });
32
+ const body = (await res.json().catch(() => null));
33
+ if (!res.ok || !body?.job)
34
+ throw new PostactError(body?.error ?? `request failed (${res.status})`, res.status);
35
+ return body.job;
36
+ },
37
+ };
38
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@postact-js/sdk",
3
+ "version": "0.1.0",
4
+ "description": "Typed client for the postact API — one POST per action.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "sideEffects": false,
18
+ "homepage": "https://postact.app",
19
+ "publishConfig": {
20
+ "access": "public"
21
+ },
22
+ "scripts": {
23
+ "build": "tsc -p tsconfig.build.json",
24
+ "prepublishOnly": "bun run build",
25
+ "typecheck": "tsc --noEmit"
26
+ },
27
+ "devDependencies": {
28
+ "@types/bun": "^1.3.14",
29
+ "typescript": "^5"
30
+ }
31
+ }