@playaos/api-client 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 +74 -0
- package/dist/applications-schema.d.ts +97 -0
- package/dist/applications-schema.js +63 -0
- package/dist/applications-schema.js.map +1 -0
- package/dist/index.d.ts +246 -0
- package/dist/index.js +188 -0
- package/dist/index.js.map +1 -0
- package/package.json +41 -0
package/README.md
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# @playaos/api-client
|
|
2
|
+
|
|
3
|
+
Typed JavaScript/TypeScript client for the [PlayaOS](https://playaos.app) REST API.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @playaos/api-client
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Quick start
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { createClient } from "@playaos/api-client";
|
|
15
|
+
|
|
16
|
+
const client = createClient({
|
|
17
|
+
baseUrl: "https://your-camp.playaos.app",
|
|
18
|
+
apiKey: "pk_live_...",
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
// List members
|
|
22
|
+
const members = await client.members.list();
|
|
23
|
+
|
|
24
|
+
// Filter by role
|
|
25
|
+
const admins = await client.members.list({ role: "admin" });
|
|
26
|
+
|
|
27
|
+
// Get a single member
|
|
28
|
+
const member = await client.members.get("uuid-here");
|
|
29
|
+
|
|
30
|
+
// List applications
|
|
31
|
+
const pending = await client.applications.list({ status: "pending", year: 2025 });
|
|
32
|
+
|
|
33
|
+
// List dues status
|
|
34
|
+
const dues = await client.dues.list({ year: 2025 });
|
|
35
|
+
|
|
36
|
+
// List shifts
|
|
37
|
+
const shifts = await client.shifts.list({ publishedOnly: true, year: 2025 });
|
|
38
|
+
|
|
39
|
+
// Get org config
|
|
40
|
+
const org = await client.org.get();
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Authentication
|
|
44
|
+
|
|
45
|
+
Generate an API key in **Admin → Settings → Developer**. Keys use the format `pk_live_*` and are scoped to specific resources.
|
|
46
|
+
|
|
47
|
+
Required scopes per endpoint:
|
|
48
|
+
|
|
49
|
+
| Endpoint | Scope |
|
|
50
|
+
|----------|-------|
|
|
51
|
+
| `members.list` / `members.get` | `members:read` |
|
|
52
|
+
| `applications.list` | `applications:read` |
|
|
53
|
+
| `dues.list` | `dues:read` |
|
|
54
|
+
| `shifts.list` | `shifts:read` |
|
|
55
|
+
| `org.get` | `org:read` |
|
|
56
|
+
|
|
57
|
+
## Error handling
|
|
58
|
+
|
|
59
|
+
```typescript
|
|
60
|
+
import { ApiClientError } from "@playaos/api-client";
|
|
61
|
+
|
|
62
|
+
try {
|
|
63
|
+
const member = await client.members.get("nonexistent-id");
|
|
64
|
+
} catch (err) {
|
|
65
|
+
if (err instanceof ApiClientError) {
|
|
66
|
+
console.error(err.status, err.code, err.message);
|
|
67
|
+
// 404, "NOT_FOUND", "Member not found"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## MCP server
|
|
73
|
+
|
|
74
|
+
For AI agent access, use the companion [@playaos/mcp](https://www.npmjs.com/package/@playaos/mcp) package.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
declare const step0Schema: z.ZodObject<{
|
|
4
|
+
acknowledged_expectations: z.ZodLiteral<true>;
|
|
5
|
+
}, z.core.$strip>;
|
|
6
|
+
declare const step1Schema: z.ZodObject<{
|
|
7
|
+
first_name: z.ZodString;
|
|
8
|
+
last_name: z.ZodString;
|
|
9
|
+
email: z.ZodString;
|
|
10
|
+
phone: z.ZodString;
|
|
11
|
+
referral_code: z.ZodOptional<z.ZodString>;
|
|
12
|
+
socials: z.ZodOptional<z.ZodString>;
|
|
13
|
+
birthday: z.ZodString;
|
|
14
|
+
hometown: z.ZodString;
|
|
15
|
+
}, z.core.$strip>;
|
|
16
|
+
declare const step2Schema: z.ZodObject<{
|
|
17
|
+
how_heard: z.ZodString;
|
|
18
|
+
burning_man_before: z.ZodEnum<{
|
|
19
|
+
yes: "yes";
|
|
20
|
+
no: "no";
|
|
21
|
+
}>;
|
|
22
|
+
burning_man_years: z.ZodOptional<z.ZodString>;
|
|
23
|
+
}, z.core.$strip>;
|
|
24
|
+
declare const step3Schema: z.ZodObject<{
|
|
25
|
+
shelter_type: z.ZodEnum<{
|
|
26
|
+
joining_rv: "joining_rv";
|
|
27
|
+
bringing_rv: "bringing_rv";
|
|
28
|
+
shiftpod_tent: "shiftpod_tent";
|
|
29
|
+
car: "car";
|
|
30
|
+
truck: "truck";
|
|
31
|
+
van: "van";
|
|
32
|
+
unsure: "unsure";
|
|
33
|
+
}>;
|
|
34
|
+
shelter_mates: z.ZodOptional<z.ZodString>;
|
|
35
|
+
ticket_status: z.ZodEnum<{
|
|
36
|
+
unsure: "unsure";
|
|
37
|
+
looking: "looking";
|
|
38
|
+
have_ticket: "have_ticket";
|
|
39
|
+
}>;
|
|
40
|
+
}, z.core.$strip>;
|
|
41
|
+
declare const step4Schema: z.ZodObject<{
|
|
42
|
+
build_available: z.ZodBoolean;
|
|
43
|
+
build_skills: z.ZodOptional<z.ZodString>;
|
|
44
|
+
strike_available: z.ZodBoolean;
|
|
45
|
+
strike_help: z.ZodOptional<z.ZodString>;
|
|
46
|
+
}, z.core.$strip>;
|
|
47
|
+
declare const step5Schema: z.ZodObject<{
|
|
48
|
+
about_yourself: z.ZodString;
|
|
49
|
+
agrees_to_principles: z.ZodLiteral<true>;
|
|
50
|
+
}, z.core.$strip>;
|
|
51
|
+
declare const applicationSchema: z.ZodObject<{
|
|
52
|
+
acknowledged_expectations: z.ZodLiteral<true>;
|
|
53
|
+
first_name: z.ZodString;
|
|
54
|
+
last_name: z.ZodString;
|
|
55
|
+
email: z.ZodString;
|
|
56
|
+
phone: z.ZodString;
|
|
57
|
+
referral_code: z.ZodOptional<z.ZodString>;
|
|
58
|
+
socials: z.ZodOptional<z.ZodString>;
|
|
59
|
+
birthday: z.ZodString;
|
|
60
|
+
hometown: z.ZodString;
|
|
61
|
+
how_heard: z.ZodString;
|
|
62
|
+
burning_man_before: z.ZodEnum<{
|
|
63
|
+
yes: "yes";
|
|
64
|
+
no: "no";
|
|
65
|
+
}>;
|
|
66
|
+
burning_man_years: z.ZodOptional<z.ZodString>;
|
|
67
|
+
shelter_type: z.ZodEnum<{
|
|
68
|
+
joining_rv: "joining_rv";
|
|
69
|
+
bringing_rv: "bringing_rv";
|
|
70
|
+
shiftpod_tent: "shiftpod_tent";
|
|
71
|
+
car: "car";
|
|
72
|
+
truck: "truck";
|
|
73
|
+
van: "van";
|
|
74
|
+
unsure: "unsure";
|
|
75
|
+
}>;
|
|
76
|
+
ticket_status: z.ZodEnum<{
|
|
77
|
+
unsure: "unsure";
|
|
78
|
+
looking: "looking";
|
|
79
|
+
have_ticket: "have_ticket";
|
|
80
|
+
}>;
|
|
81
|
+
shelter_mates: z.ZodOptional<z.ZodString>;
|
|
82
|
+
build_available: z.ZodBoolean;
|
|
83
|
+
build_skills: z.ZodOptional<z.ZodString>;
|
|
84
|
+
strike_available: z.ZodBoolean;
|
|
85
|
+
strike_help: z.ZodOptional<z.ZodString>;
|
|
86
|
+
about_yourself: z.ZodString;
|
|
87
|
+
agrees_to_principles: z.ZodLiteral<true>;
|
|
88
|
+
}, z.core.$strip>;
|
|
89
|
+
type ApplicationFormData = z.infer<typeof applicationSchema>;
|
|
90
|
+
type Step0Data = z.infer<typeof step0Schema>;
|
|
91
|
+
type Step1Data = z.infer<typeof step1Schema>;
|
|
92
|
+
type Step2Data = z.infer<typeof step2Schema>;
|
|
93
|
+
type Step3Data = z.infer<typeof step3Schema>;
|
|
94
|
+
type Step4Data = z.infer<typeof step4Schema>;
|
|
95
|
+
type Step5Data = z.infer<typeof step5Schema>;
|
|
96
|
+
|
|
97
|
+
export { type ApplicationFormData, type Step0Data, type Step1Data, type Step2Data, type Step3Data, type Step4Data, type Step5Data, applicationSchema, step0Schema, step1Schema, step2Schema, step3Schema, step4Schema, step5Schema };
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// src/applications-schema.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var step0Schema = z.object({
|
|
4
|
+
acknowledged_expectations: z.literal(true, {
|
|
5
|
+
error: "Please acknowledge the expectations to continue"
|
|
6
|
+
})
|
|
7
|
+
});
|
|
8
|
+
var step1Schema = z.object({
|
|
9
|
+
first_name: z.string().min(1, "First name is required"),
|
|
10
|
+
last_name: z.string().min(1, "Last name is required"),
|
|
11
|
+
email: z.string().email("Invalid email address"),
|
|
12
|
+
phone: z.string().min(10, "Phone number is required"),
|
|
13
|
+
referral_code: z.string().optional(),
|
|
14
|
+
socials: z.string().optional(),
|
|
15
|
+
birthday: z.string().min(1, "Birthday is required"),
|
|
16
|
+
hometown: z.string().min(1, "Location is required")
|
|
17
|
+
});
|
|
18
|
+
var step2Schema = z.object({
|
|
19
|
+
how_heard: z.string().min(1, "Please tell us how you heard about us"),
|
|
20
|
+
burning_man_before: z.enum(["yes", "no"], { error: "Please select an option" }),
|
|
21
|
+
burning_man_years: z.string().optional()
|
|
22
|
+
});
|
|
23
|
+
var step3Schema = z.object({
|
|
24
|
+
shelter_type: z.enum(["joining_rv", "bringing_rv", "shiftpod_tent", "car", "truck", "van", "unsure"], {
|
|
25
|
+
error: "Please select a shelter type"
|
|
26
|
+
}),
|
|
27
|
+
shelter_mates: z.string().optional(),
|
|
28
|
+
ticket_status: z.enum(["looking", "have_ticket", "unsure"], { error: "Please select your ticket status" })
|
|
29
|
+
});
|
|
30
|
+
var step4Schema = z.object({
|
|
31
|
+
build_available: z.boolean(),
|
|
32
|
+
build_skills: z.string().optional(),
|
|
33
|
+
strike_available: z.boolean(),
|
|
34
|
+
strike_help: z.string().optional()
|
|
35
|
+
}).refine((data) => !data.build_available || data.build_skills, {
|
|
36
|
+
message: "Please tell us about your build skills",
|
|
37
|
+
path: ["build_skills"]
|
|
38
|
+
}).refine((data) => !data.strike_available || data.strike_help, {
|
|
39
|
+
message: "Please tell us how you can help with strike",
|
|
40
|
+
path: ["strike_help"]
|
|
41
|
+
});
|
|
42
|
+
var step5Schema = z.object({
|
|
43
|
+
about_yourself: z.string().min(50, "Please write at least 50 characters about yourself"),
|
|
44
|
+
agrees_to_principles: z.literal(true, { error: "You must agree to the 10 Principles" })
|
|
45
|
+
});
|
|
46
|
+
var applicationSchema = step0Schema.merge(step1Schema).merge(step2Schema).merge(step3Schema.omit({ shelter_mates: true })).extend({ shelter_mates: z.string().optional() }).merge(
|
|
47
|
+
z.object({
|
|
48
|
+
build_available: z.boolean(),
|
|
49
|
+
build_skills: z.string().optional(),
|
|
50
|
+
strike_available: z.boolean(),
|
|
51
|
+
strike_help: z.string().optional()
|
|
52
|
+
})
|
|
53
|
+
).merge(step5Schema);
|
|
54
|
+
export {
|
|
55
|
+
applicationSchema,
|
|
56
|
+
step0Schema,
|
|
57
|
+
step1Schema,
|
|
58
|
+
step2Schema,
|
|
59
|
+
step3Schema,
|
|
60
|
+
step4Schema,
|
|
61
|
+
step5Schema
|
|
62
|
+
};
|
|
63
|
+
//# sourceMappingURL=applications-schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/applications-schema.ts"],"sourcesContent":["import { z } from \"zod\";\n\n// Single source of truth for the apply-wizard form shape.\n// The portal apply flow and the @playaos/react <ApplicationForm> SDK both import\n// from here so the validation contract can never drift between them (PLA-483).\n// `ApplicationCreatePayload` in ./types.ts is the zod-free wire mirror; a type\n// equivalence test (applications-schema.test.ts) keeps the two locked together.\n\n// Per-step validation schemas\nexport const step0Schema = z.object({\n acknowledged_expectations: z.literal(true, {\n error: \"Please acknowledge the expectations to continue\",\n }),\n});\n\nexport const step1Schema = z.object({\n first_name: z.string().min(1, \"First name is required\"),\n last_name: z.string().min(1, \"Last name is required\"),\n email: z.string().email(\"Invalid email address\"),\n phone: z.string().min(10, \"Phone number is required\"),\n referral_code: z.string().optional(),\n socials: z.string().optional(),\n birthday: z.string().min(1, \"Birthday is required\"),\n hometown: z.string().min(1, \"Location is required\"),\n});\n\nexport const step2Schema = z.object({\n how_heard: z.string().min(1, \"Please tell us how you heard about us\"),\n burning_man_before: z.enum([\"yes\", \"no\"], { error: \"Please select an option\" }),\n burning_man_years: z.string().optional(),\n});\n\nexport const step3Schema = z.object({\n shelter_type: z.enum([\"joining_rv\", \"bringing_rv\", \"shiftpod_tent\", \"car\", \"truck\", \"van\", \"unsure\"], {\n error: \"Please select a shelter type\",\n }),\n shelter_mates: z.string().optional(),\n ticket_status: z.enum([\"looking\", \"have_ticket\", \"unsure\"], { error: \"Please select your ticket status\" }),\n});\n\nexport const step4Schema = z\n .object({\n build_available: z.boolean(),\n build_skills: z.string().optional(),\n strike_available: z.boolean(),\n strike_help: z.string().optional(),\n })\n .refine((data) => !data.build_available || data.build_skills, {\n message: \"Please tell us about your build skills\",\n path: [\"build_skills\"],\n })\n .refine((data) => !data.strike_available || data.strike_help, {\n message: \"Please tell us how you can help with strike\",\n path: [\"strike_help\"],\n });\n\nexport const step5Schema = z.object({\n about_yourself: z.string().min(50, \"Please write at least 50 characters about yourself\"),\n agrees_to_principles: z.literal(true, { error: \"You must agree to the 10 Principles\" }),\n});\n\n// Combined schema for full submission\nexport const applicationSchema = step0Schema\n .merge(step1Schema)\n .merge(step2Schema)\n .merge(step3Schema.omit({ shelter_mates: true }))\n .extend({ shelter_mates: z.string().optional() })\n .merge(\n z.object({\n build_available: z.boolean(),\n build_skills: z.string().optional(),\n strike_available: z.boolean(),\n strike_help: z.string().optional(),\n }),\n )\n .merge(step5Schema);\n\nexport type ApplicationFormData = z.infer<typeof applicationSchema>;\nexport type Step0Data = z.infer<typeof step0Schema>;\nexport type Step1Data = z.infer<typeof step1Schema>;\nexport type Step2Data = z.infer<typeof step2Schema>;\nexport type Step3Data = z.infer<typeof step3Schema>;\nexport type Step4Data = z.infer<typeof step4Schema>;\nexport type Step5Data = z.infer<typeof step5Schema>;\n"],"mappings":";AAAA,SAAS,SAAS;AASX,IAAM,cAAc,EAAE,OAAO;AAAA,EAClC,2BAA2B,EAAE,QAAQ,MAAM;AAAA,IACzC,OAAO;AAAA,EACT,CAAC;AACH,CAAC;AAEM,IAAM,cAAc,EAAE,OAAO;AAAA,EAClC,YAAY,EAAE,OAAO,EAAE,IAAI,GAAG,wBAAwB;AAAA,EACtD,WAAW,EAAE,OAAO,EAAE,IAAI,GAAG,uBAAuB;AAAA,EACpD,OAAO,EAAE,OAAO,EAAE,MAAM,uBAAuB;AAAA,EAC/C,OAAO,EAAE,OAAO,EAAE,IAAI,IAAI,0BAA0B;AAAA,EACpD,eAAe,EAAE,OAAO,EAAE,SAAS;AAAA,EACnC,SAAS,EAAE,OAAO,EAAE,SAAS;AAAA,EAC7B,UAAU,EAAE,OAAO,EAAE,IAAI,GAAG,sBAAsB;AAAA,EAClD,UAAU,EAAE,OAAO,EAAE,IAAI,GAAG,sBAAsB;AACpD,CAAC;AAEM,IAAM,cAAc,EAAE,OAAO;AAAA,EAClC,WAAW,EAAE,OAAO,EAAE,IAAI,GAAG,uCAAuC;AAAA,EACpE,oBAAoB,EAAE,KAAK,CAAC,OAAO,IAAI,GAAG,EAAE,OAAO,0BAA0B,CAAC;AAAA,EAC9E,mBAAmB,EAAE,OAAO,EAAE,SAAS;AACzC,CAAC;AAEM,IAAM,cAAc,EAAE,OAAO;AAAA,EAClC,cAAc,EAAE,KAAK,CAAC,cAAc,eAAe,iBAAiB,OAAO,SAAS,OAAO,QAAQ,GAAG;AAAA,IACpG,OAAO;AAAA,EACT,CAAC;AAAA,EACD,eAAe,EAAE,OAAO,EAAE,SAAS;AAAA,EACnC,eAAe,EAAE,KAAK,CAAC,WAAW,eAAe,QAAQ,GAAG,EAAE,OAAO,mCAAmC,CAAC;AAC3G,CAAC;AAEM,IAAM,cAAc,EACxB,OAAO;AAAA,EACN,iBAAiB,EAAE,QAAQ;AAAA,EAC3B,cAAc,EAAE,OAAO,EAAE,SAAS;AAAA,EAClC,kBAAkB,EAAE,QAAQ;AAAA,EAC5B,aAAa,EAAE,OAAO,EAAE,SAAS;AACnC,CAAC,EACA,OAAO,CAAC,SAAS,CAAC,KAAK,mBAAmB,KAAK,cAAc;AAAA,EAC5D,SAAS;AAAA,EACT,MAAM,CAAC,cAAc;AACvB,CAAC,EACA,OAAO,CAAC,SAAS,CAAC,KAAK,oBAAoB,KAAK,aAAa;AAAA,EAC5D,SAAS;AAAA,EACT,MAAM,CAAC,aAAa;AACtB,CAAC;AAEI,IAAM,cAAc,EAAE,OAAO;AAAA,EAClC,gBAAgB,EAAE,OAAO,EAAE,IAAI,IAAI,oDAAoD;AAAA,EACvF,sBAAsB,EAAE,QAAQ,MAAM,EAAE,OAAO,sCAAsC,CAAC;AACxF,CAAC;AAGM,IAAM,oBAAoB,YAC9B,MAAM,WAAW,EACjB,MAAM,WAAW,EACjB,MAAM,YAAY,KAAK,EAAE,eAAe,KAAK,CAAC,CAAC,EAC/C,OAAO,EAAE,eAAe,EAAE,OAAO,EAAE,SAAS,EAAE,CAAC,EAC/C;AAAA,EACC,EAAE,OAAO;AAAA,IACP,iBAAiB,EAAE,QAAQ;AAAA,IAC3B,cAAc,EAAE,OAAO,EAAE,SAAS;AAAA,IAClC,kBAAkB,EAAE,QAAQ;AAAA,IAC5B,aAAa,EAAE,OAAO,EAAE,SAAS;AAAA,EACnC,CAAC;AACH,EACC,MAAM,WAAW;","names":[]}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
declare class ApiClientError extends Error {
|
|
2
|
+
readonly status: number;
|
|
3
|
+
readonly code: string;
|
|
4
|
+
constructor(status: number, code: string, message: string);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface Member {
|
|
8
|
+
id: string;
|
|
9
|
+
firstName: string;
|
|
10
|
+
lastName: string;
|
|
11
|
+
name: string | null;
|
|
12
|
+
email: string;
|
|
13
|
+
role: string | null;
|
|
14
|
+
status: string | null;
|
|
15
|
+
orgId: string;
|
|
16
|
+
createdAt: string | null;
|
|
17
|
+
}
|
|
18
|
+
interface Application {
|
|
19
|
+
id: string;
|
|
20
|
+
userId: string;
|
|
21
|
+
orgId: string;
|
|
22
|
+
status: string | null;
|
|
23
|
+
year: number;
|
|
24
|
+
createdAt: string | null;
|
|
25
|
+
updatedAt: string | null;
|
|
26
|
+
}
|
|
27
|
+
interface DuesStatus {
|
|
28
|
+
userId: string | null;
|
|
29
|
+
year: number | null;
|
|
30
|
+
paymentStatus: string | null;
|
|
31
|
+
amountOwed: string | null;
|
|
32
|
+
amountPaid: string | null;
|
|
33
|
+
balance: string | null;
|
|
34
|
+
}
|
|
35
|
+
interface ShiftSignup {
|
|
36
|
+
id: string;
|
|
37
|
+
userId: string;
|
|
38
|
+
status: string | null;
|
|
39
|
+
role: string | null;
|
|
40
|
+
}
|
|
41
|
+
interface ShiftType {
|
|
42
|
+
id: string;
|
|
43
|
+
name: string;
|
|
44
|
+
description: string | null;
|
|
45
|
+
}
|
|
46
|
+
interface Shift {
|
|
47
|
+
id: string;
|
|
48
|
+
orgId: string;
|
|
49
|
+
shiftDate: string;
|
|
50
|
+
year: number;
|
|
51
|
+
startTime: string | null;
|
|
52
|
+
endTime: string | null;
|
|
53
|
+
maxSlots: number | null;
|
|
54
|
+
isPublished: boolean | null;
|
|
55
|
+
shiftType: ShiftType | null;
|
|
56
|
+
shiftSignups: ShiftSignup[];
|
|
57
|
+
}
|
|
58
|
+
interface OrgConfig {
|
|
59
|
+
id: string;
|
|
60
|
+
name: string;
|
|
61
|
+
slug: string;
|
|
62
|
+
preset: string | null;
|
|
63
|
+
customDomain: string | null;
|
|
64
|
+
settings: Record<string, unknown> | null;
|
|
65
|
+
features: Record<string, unknown> | null;
|
|
66
|
+
plan: string | null;
|
|
67
|
+
logoUrl: string | null;
|
|
68
|
+
}
|
|
69
|
+
type ApplicationStatus = "draft" | "pending" | "invite_sent" | "interview_scheduled" | "interviewed" | "approved" | "waitlisted" | "rejected";
|
|
70
|
+
type MemberRole = "member" | "admin" | "applicant" | "super_admin";
|
|
71
|
+
interface PaymentPageResponse {
|
|
72
|
+
provider: "givebutter" | "crowded";
|
|
73
|
+
url: string;
|
|
74
|
+
modalSupported: boolean;
|
|
75
|
+
}
|
|
76
|
+
interface ApplicationCreatePayload {
|
|
77
|
+
acknowledged_expectations: true;
|
|
78
|
+
first_name: string;
|
|
79
|
+
last_name: string;
|
|
80
|
+
email: string;
|
|
81
|
+
phone: string;
|
|
82
|
+
referral_code?: string;
|
|
83
|
+
socials?: string;
|
|
84
|
+
birthday: string;
|
|
85
|
+
hometown: string;
|
|
86
|
+
how_heard: string;
|
|
87
|
+
burning_man_before: "yes" | "no";
|
|
88
|
+
burning_man_years?: string;
|
|
89
|
+
shelter_type: "joining_rv" | "bringing_rv" | "shiftpod_tent" | "car" | "truck" | "van" | "unsure";
|
|
90
|
+
shelter_mates?: string;
|
|
91
|
+
ticket_status: "looking" | "have_ticket" | "unsure";
|
|
92
|
+
build_available: boolean;
|
|
93
|
+
build_skills?: string;
|
|
94
|
+
strike_available: boolean;
|
|
95
|
+
strike_help?: string;
|
|
96
|
+
about_yourself: string;
|
|
97
|
+
agrees_to_principles: true;
|
|
98
|
+
}
|
|
99
|
+
interface ApplicationCreateResponse {
|
|
100
|
+
applicationId: string;
|
|
101
|
+
status: string;
|
|
102
|
+
}
|
|
103
|
+
interface ApplicationTransitionPayload {
|
|
104
|
+
to: ApplicationStatus;
|
|
105
|
+
rejectionReason?: string;
|
|
106
|
+
}
|
|
107
|
+
interface ApplicationTransitionResponse {
|
|
108
|
+
applicationId: string;
|
|
109
|
+
status: ApplicationStatus;
|
|
110
|
+
}
|
|
111
|
+
interface MemberCreatePayload {
|
|
112
|
+
firstName: string;
|
|
113
|
+
lastName: string;
|
|
114
|
+
email: string;
|
|
115
|
+
phone?: string | null;
|
|
116
|
+
role?: "member" | "admin" | "super_admin";
|
|
117
|
+
status?: string;
|
|
118
|
+
}
|
|
119
|
+
interface MemberCreateResponse {
|
|
120
|
+
profileId: string;
|
|
121
|
+
}
|
|
122
|
+
interface MemberUpdatePayload {
|
|
123
|
+
firstName?: string;
|
|
124
|
+
lastName?: string;
|
|
125
|
+
phone?: string | null;
|
|
126
|
+
}
|
|
127
|
+
interface MemberUpdateResponse {
|
|
128
|
+
profileId: string;
|
|
129
|
+
}
|
|
130
|
+
interface MemberDeactivateResponse {
|
|
131
|
+
profileId: string;
|
|
132
|
+
status: "inactive";
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
interface ClientOptions {
|
|
136
|
+
/** Base URL of the PlayaOS portal, e.g. https://your-camp.playaos.app */
|
|
137
|
+
baseUrl: string;
|
|
138
|
+
/** API key in the format pk_live_* */
|
|
139
|
+
apiKey: string;
|
|
140
|
+
}
|
|
141
|
+
declare class PlayaOSClient {
|
|
142
|
+
private readonly baseUrl;
|
|
143
|
+
private readonly apiKey;
|
|
144
|
+
constructor(opts: ClientOptions);
|
|
145
|
+
readonly members: {
|
|
146
|
+
list: (params?: {
|
|
147
|
+
role?: MemberRole;
|
|
148
|
+
status?: string;
|
|
149
|
+
}, opts?: {
|
|
150
|
+
signal?: AbortSignal;
|
|
151
|
+
}) => Promise<Member[]>;
|
|
152
|
+
get: (id: string, opts?: {
|
|
153
|
+
signal?: AbortSignal;
|
|
154
|
+
}) => Promise<Member>;
|
|
155
|
+
};
|
|
156
|
+
readonly applications: {
|
|
157
|
+
list: (params?: {
|
|
158
|
+
status?: ApplicationStatus;
|
|
159
|
+
year?: number;
|
|
160
|
+
}, opts?: {
|
|
161
|
+
signal?: AbortSignal;
|
|
162
|
+
}) => Promise<Application[]>;
|
|
163
|
+
create: (payload: ApplicationCreatePayload, opts?: {
|
|
164
|
+
accessToken?: string;
|
|
165
|
+
signal?: AbortSignal;
|
|
166
|
+
}) => Promise<ApplicationCreateResponse>;
|
|
167
|
+
};
|
|
168
|
+
readonly dues: {
|
|
169
|
+
list: (params?: {
|
|
170
|
+
userId?: string;
|
|
171
|
+
year?: number;
|
|
172
|
+
}, opts?: {
|
|
173
|
+
signal?: AbortSignal;
|
|
174
|
+
}) => Promise<DuesStatus[]>;
|
|
175
|
+
};
|
|
176
|
+
readonly shifts: {
|
|
177
|
+
list: (params?: {
|
|
178
|
+
year?: number;
|
|
179
|
+
fromDate?: string;
|
|
180
|
+
toDate?: string;
|
|
181
|
+
publishedOnly?: boolean;
|
|
182
|
+
}, opts?: {
|
|
183
|
+
signal?: AbortSignal;
|
|
184
|
+
}) => Promise<Shift[]>;
|
|
185
|
+
};
|
|
186
|
+
readonly org: {
|
|
187
|
+
get: (opts?: {
|
|
188
|
+
signal?: AbortSignal;
|
|
189
|
+
}) => Promise<OrgConfig>;
|
|
190
|
+
};
|
|
191
|
+
readonly payments: {
|
|
192
|
+
page: (memberId: string, duesCollectionId?: string, opts?: {
|
|
193
|
+
signal?: AbortSignal;
|
|
194
|
+
}) => Promise<PaymentPageResponse>;
|
|
195
|
+
};
|
|
196
|
+
readonly admin: {
|
|
197
|
+
applications: {
|
|
198
|
+
transition: (id: string, payload: ApplicationTransitionPayload, opts: {
|
|
199
|
+
accessToken: string;
|
|
200
|
+
signal?: AbortSignal;
|
|
201
|
+
}) => Promise<ApplicationTransitionResponse>;
|
|
202
|
+
};
|
|
203
|
+
members: {
|
|
204
|
+
create: (payload: MemberCreatePayload, opts: {
|
|
205
|
+
accessToken: string;
|
|
206
|
+
signal?: AbortSignal;
|
|
207
|
+
}) => Promise<MemberCreateResponse>;
|
|
208
|
+
update: (id: string, patch: MemberUpdatePayload, opts: {
|
|
209
|
+
accessToken: string;
|
|
210
|
+
signal?: AbortSignal;
|
|
211
|
+
}) => Promise<MemberUpdateResponse>;
|
|
212
|
+
deactivate: (id: string, opts: {
|
|
213
|
+
accessToken: string;
|
|
214
|
+
signal?: AbortSignal;
|
|
215
|
+
}) => Promise<MemberDeactivateResponse>;
|
|
216
|
+
};
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
/** Convenience factory — equivalent to `new PlayaOSClient(opts)`. */
|
|
220
|
+
declare function createClient(opts: ClientOptions): PlayaOSClient;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Server-side OAuth-code → ID-token exchange (PLA-573 T24).
|
|
224
|
+
*
|
|
225
|
+
* Used by camp sites' confidential-client backends to swap a one-time
|
|
226
|
+
* authorization code from the PlayaOS IdP for a short-lived ID token.
|
|
227
|
+
* Public/PKCE-only clients exchange directly from the browser via
|
|
228
|
+
* `usePlayaOSAuth` in `@playaos/react`; this helper exists for confidential
|
|
229
|
+
* clients that need to send the client_secret server-side.
|
|
230
|
+
*/
|
|
231
|
+
interface ExchangeParams {
|
|
232
|
+
authBaseUrl?: string;
|
|
233
|
+
code: string;
|
|
234
|
+
codeVerifier: string;
|
|
235
|
+
clientId: string;
|
|
236
|
+
clientSecret: string;
|
|
237
|
+
redirectUri: string;
|
|
238
|
+
}
|
|
239
|
+
interface ExchangeResult {
|
|
240
|
+
idToken: string;
|
|
241
|
+
expiresAt: string;
|
|
242
|
+
refreshToken: string;
|
|
243
|
+
}
|
|
244
|
+
declare function exchangeCode(params: ExchangeParams): Promise<ExchangeResult>;
|
|
245
|
+
|
|
246
|
+
export { ApiClientError, type Application, type ApplicationCreatePayload, type ApplicationCreateResponse, type ApplicationStatus, type ApplicationTransitionPayload, type ApplicationTransitionResponse, type DuesStatus, type ExchangeParams, type ExchangeResult, type Member, type MemberCreatePayload, type MemberCreateResponse, type MemberDeactivateResponse, type MemberRole, type MemberUpdatePayload, type MemberUpdateResponse, type OrgConfig, type PaymentPageResponse, PlayaOSClient, type Shift, type ShiftSignup, type ShiftType, createClient, exchangeCode };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
// src/error.ts
|
|
2
|
+
var ApiClientError = class extends Error {
|
|
3
|
+
constructor(status, code, message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.status = status;
|
|
6
|
+
this.code = code;
|
|
7
|
+
this.name = "ApiClientError";
|
|
8
|
+
}
|
|
9
|
+
status;
|
|
10
|
+
code;
|
|
11
|
+
};
|
|
12
|
+
async function parseError(res) {
|
|
13
|
+
try {
|
|
14
|
+
const body = await res.json();
|
|
15
|
+
return new ApiClientError(res.status, body.code ?? "UNKNOWN", body.error ?? res.statusText);
|
|
16
|
+
} catch {
|
|
17
|
+
return new ApiClientError(res.status, "UNKNOWN", res.statusText);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// src/client.ts
|
|
22
|
+
function buildUrl(base, path, params) {
|
|
23
|
+
const url = new URL(`${base}/api/v1${path}`);
|
|
24
|
+
if (params) {
|
|
25
|
+
for (const [k, v] of Object.entries(params)) {
|
|
26
|
+
if (v !== void 0 && v !== false) url.searchParams.set(k, String(v));
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return url.toString();
|
|
30
|
+
}
|
|
31
|
+
function buildEmbedUrl(base, path) {
|
|
32
|
+
return `${base}/api/embed/v1${path}`;
|
|
33
|
+
}
|
|
34
|
+
async function request(url, apiKey, signal) {
|
|
35
|
+
const res = await fetch(url, {
|
|
36
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
37
|
+
signal
|
|
38
|
+
});
|
|
39
|
+
if (!res.ok) throw await parseError(res);
|
|
40
|
+
return res.json();
|
|
41
|
+
}
|
|
42
|
+
async function post(url, apiKey, body, signal) {
|
|
43
|
+
const res = await fetch(url, {
|
|
44
|
+
method: "POST",
|
|
45
|
+
headers: { Authorization: `Bearer ${apiKey}`, "Content-Type": "application/json" },
|
|
46
|
+
body: JSON.stringify(body),
|
|
47
|
+
signal
|
|
48
|
+
});
|
|
49
|
+
if (!res.ok) throw await parseError(res);
|
|
50
|
+
return res.json();
|
|
51
|
+
}
|
|
52
|
+
async function postEmbed(url, campKey, body, opts) {
|
|
53
|
+
const headers = {
|
|
54
|
+
"X-Camp-Key": campKey,
|
|
55
|
+
"Content-Type": "application/json"
|
|
56
|
+
};
|
|
57
|
+
if (opts?.accessToken) headers.Authorization = `Bearer ${opts.accessToken}`;
|
|
58
|
+
const res = await fetch(url, {
|
|
59
|
+
method: "POST",
|
|
60
|
+
headers,
|
|
61
|
+
body: JSON.stringify(body),
|
|
62
|
+
signal: opts?.signal
|
|
63
|
+
});
|
|
64
|
+
if (!res.ok) throw await parseError(res);
|
|
65
|
+
return res.json();
|
|
66
|
+
}
|
|
67
|
+
async function adminEmbed(method, url, accessToken, body, signal) {
|
|
68
|
+
const res = await fetch(url, {
|
|
69
|
+
method,
|
|
70
|
+
headers: { Authorization: `Bearer ${accessToken}`, "Content-Type": "application/json" },
|
|
71
|
+
body: JSON.stringify(body),
|
|
72
|
+
signal
|
|
73
|
+
});
|
|
74
|
+
if (!res.ok) throw await parseError(res);
|
|
75
|
+
return res.json();
|
|
76
|
+
}
|
|
77
|
+
var PlayaOSClient = class {
|
|
78
|
+
baseUrl;
|
|
79
|
+
apiKey;
|
|
80
|
+
constructor(opts) {
|
|
81
|
+
this.baseUrl = opts.baseUrl.replace(/\/$/, "");
|
|
82
|
+
this.apiKey = opts.apiKey;
|
|
83
|
+
}
|
|
84
|
+
members = {
|
|
85
|
+
list: (params, opts) => request(buildUrl(this.baseUrl, "/members", params), this.apiKey, opts?.signal),
|
|
86
|
+
get: (id, opts) => request(buildUrl(this.baseUrl, `/members/${encodeURIComponent(id)}`), this.apiKey, opts?.signal)
|
|
87
|
+
};
|
|
88
|
+
applications = {
|
|
89
|
+
list: (params, opts) => request(buildUrl(this.baseUrl, "/applications", params), this.apiKey, opts?.signal),
|
|
90
|
+
// Anonymous external-camp submission against POST /api/embed/v1/applications
|
|
91
|
+
// (PLA-481). Uses the X-Camp-Key header rather than Bearer auth. Pass
|
|
92
|
+
// `accessToken` to additionally send a PlayaOS IdP JWT (PLA-573 path) so
|
|
93
|
+
// the server trusts the verified profile claim and skips the email-based
|
|
94
|
+
// bootstrap.
|
|
95
|
+
create: (payload, opts) => postEmbed(buildEmbedUrl(this.baseUrl, "/applications"), this.apiKey, payload, opts)
|
|
96
|
+
};
|
|
97
|
+
dues = {
|
|
98
|
+
list: (params, opts) => request(buildUrl(this.baseUrl, "/dues", params), this.apiKey, opts?.signal)
|
|
99
|
+
};
|
|
100
|
+
shifts = {
|
|
101
|
+
list: (params, opts) => request(buildUrl(this.baseUrl, "/shifts", params), this.apiKey, opts?.signal)
|
|
102
|
+
};
|
|
103
|
+
org = {
|
|
104
|
+
get: (opts) => request(buildUrl(this.baseUrl, "/org"), this.apiKey, opts?.signal)
|
|
105
|
+
};
|
|
106
|
+
payments = {
|
|
107
|
+
page: (memberId, duesCollectionId, opts) => post(buildUrl(this.baseUrl, "/payments/page"), this.apiKey, { memberId, duesCollectionId }, opts?.signal)
|
|
108
|
+
};
|
|
109
|
+
// Admin write APIs (PLA-489). Each method requires `accessToken` — a Supabase
|
|
110
|
+
// JWT for a camp admin/super_admin. The org boundary is enforced server-side
|
|
111
|
+
// from the JWT claims; these endpoints do not use the X-Camp-Key.
|
|
112
|
+
admin = {
|
|
113
|
+
applications: {
|
|
114
|
+
transition: (id, payload, opts) => adminEmbed(
|
|
115
|
+
"PATCH",
|
|
116
|
+
buildEmbedUrl(this.baseUrl, `/admin/applications/${encodeURIComponent(id)}`),
|
|
117
|
+
opts.accessToken,
|
|
118
|
+
payload,
|
|
119
|
+
opts.signal
|
|
120
|
+
)
|
|
121
|
+
},
|
|
122
|
+
members: {
|
|
123
|
+
create: (payload, opts) => adminEmbed("POST", buildEmbedUrl(this.baseUrl, "/admin/members"), opts.accessToken, payload, opts.signal),
|
|
124
|
+
update: (id, patch, opts) => adminEmbed(
|
|
125
|
+
"PATCH",
|
|
126
|
+
buildEmbedUrl(this.baseUrl, `/admin/members/${encodeURIComponent(id)}`),
|
|
127
|
+
opts.accessToken,
|
|
128
|
+
patch,
|
|
129
|
+
opts.signal
|
|
130
|
+
),
|
|
131
|
+
deactivate: (id, opts) => adminEmbed(
|
|
132
|
+
"PATCH",
|
|
133
|
+
buildEmbedUrl(this.baseUrl, `/admin/members/${encodeURIComponent(id)}`),
|
|
134
|
+
opts.accessToken,
|
|
135
|
+
{ status: "inactive" },
|
|
136
|
+
opts.signal
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
function createClient(opts) {
|
|
142
|
+
return new PlayaOSClient(opts);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// src/exchange.ts
|
|
146
|
+
async function exchangeCode(params) {
|
|
147
|
+
const base = params.authBaseUrl ?? "https://auth.playaos.app";
|
|
148
|
+
const res = await fetch(`${base}/api/auth/v1/exchange`, {
|
|
149
|
+
method: "POST",
|
|
150
|
+
headers: { "content-type": "application/json" },
|
|
151
|
+
body: JSON.stringify({
|
|
152
|
+
code: params.code,
|
|
153
|
+
code_verifier: params.codeVerifier,
|
|
154
|
+
client_id: params.clientId,
|
|
155
|
+
client_secret: params.clientSecret,
|
|
156
|
+
redirect_uri: params.redirectUri
|
|
157
|
+
})
|
|
158
|
+
});
|
|
159
|
+
let body;
|
|
160
|
+
try {
|
|
161
|
+
body = await res.json();
|
|
162
|
+
} catch {
|
|
163
|
+
throw new Error(`Exchange failed: non-JSON response (${res.status})`);
|
|
164
|
+
}
|
|
165
|
+
if (!res.ok) {
|
|
166
|
+
const errorReason = typeof body === "object" && body !== null && "error" in body && typeof body.error === "string" ? body.error : String(res.status);
|
|
167
|
+
throw new Error(`Exchange failed: ${errorReason}`);
|
|
168
|
+
}
|
|
169
|
+
if (typeof body !== "object" || body === null || !("idToken" in body) || typeof body.idToken !== "string" || !("expiresAt" in body) || typeof body.expiresAt !== "string") {
|
|
170
|
+
throw new Error("Exchange failed: malformed success response");
|
|
171
|
+
}
|
|
172
|
+
const refreshToken = "refreshToken" in body && typeof body.refreshToken === "string" ? body.refreshToken : void 0;
|
|
173
|
+
if (refreshToken === void 0) {
|
|
174
|
+
throw new Error("Exchange failed: missing refreshToken in confidential response");
|
|
175
|
+
}
|
|
176
|
+
return {
|
|
177
|
+
idToken: body.idToken,
|
|
178
|
+
expiresAt: body.expiresAt,
|
|
179
|
+
refreshToken
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
export {
|
|
183
|
+
ApiClientError,
|
|
184
|
+
PlayaOSClient,
|
|
185
|
+
createClient,
|
|
186
|
+
exchangeCode
|
|
187
|
+
};
|
|
188
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/error.ts","../src/client.ts","../src/exchange.ts"],"sourcesContent":["export class ApiClientError extends Error {\n constructor(\n public readonly status: number,\n public readonly code: string,\n message: string,\n ) {\n super(message);\n this.name = \"ApiClientError\";\n }\n}\n\nexport async function parseError(res: Response): Promise<ApiClientError> {\n try {\n const body = (await res.json()) as { error?: string; code?: string };\n return new ApiClientError(res.status, body.code ?? \"UNKNOWN\", body.error ?? res.statusText);\n } catch {\n return new ApiClientError(res.status, \"UNKNOWN\", res.statusText);\n }\n}\n","import { type ApiClientError, parseError } from \"./error.js\";\nimport type {\n Application,\n ApplicationCreatePayload,\n ApplicationCreateResponse,\n ApplicationStatus,\n ApplicationTransitionPayload,\n ApplicationTransitionResponse,\n DuesStatus,\n Member,\n MemberCreatePayload,\n MemberCreateResponse,\n MemberDeactivateResponse,\n MemberRole,\n MemberUpdatePayload,\n MemberUpdateResponse,\n OrgConfig,\n PaymentPageResponse,\n Shift,\n} from \"./types.js\";\n\nexport type { ApiClientError };\n\ninterface ClientOptions {\n /** Base URL of the PlayaOS portal, e.g. https://your-camp.playaos.app */\n baseUrl: string;\n /** API key in the format pk_live_* */\n apiKey: string;\n}\n\nfunction buildUrl(base: string, path: string, params?: Record<string, string | number | boolean | undefined>): string {\n const url = new URL(`${base}/api/v1${path}`);\n if (params) {\n for (const [k, v] of Object.entries(params)) {\n // Skip undefined and false booleans — z.coerce.boolean() on the server uses Boolean(),\n // which treats any non-empty string (including \"false\") as true. Omitting a false boolean\n // param has the same effect as the server default (unfiltered).\n if (v !== undefined && v !== false) url.searchParams.set(k, String(v));\n }\n }\n return url.toString();\n}\n\nfunction buildEmbedUrl(base: string, path: string): string {\n return `${base}/api/embed/v1${path}`;\n}\n\nasync function request<T>(url: string, apiKey: string, signal?: AbortSignal): Promise<T> {\n const res = await fetch(url, {\n headers: { Authorization: `Bearer ${apiKey}` },\n signal,\n });\n if (!res.ok) throw await parseError(res);\n return res.json() as Promise<T>;\n}\n\nasync function post<T>(url: string, apiKey: string, body: unknown, signal?: AbortSignal): Promise<T> {\n const res = await fetch(url, {\n method: \"POST\",\n headers: { Authorization: `Bearer ${apiKey}`, \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n signal,\n });\n if (!res.ok) throw await parseError(res);\n return res.json() as Promise<T>;\n}\n\nasync function postEmbed<T>(\n url: string,\n campKey: string,\n body: unknown,\n opts?: { accessToken?: string; signal?: AbortSignal },\n): Promise<T> {\n const headers: Record<string, string> = {\n \"X-Camp-Key\": campKey,\n \"Content-Type\": \"application/json\",\n };\n if (opts?.accessToken) headers.Authorization = `Bearer ${opts.accessToken}`;\n const res = await fetch(url, {\n method: \"POST\",\n headers,\n body: JSON.stringify(body),\n signal: opts?.signal,\n });\n if (!res.ok) throw await parseError(res);\n return res.json() as Promise<T>;\n}\n\n/**\n * Admin write helper for /api/embed/v1/admin/* (PLA-489). Bearer-only: the\n * admin token is the sole credential (no X-Camp-Key — orgId comes from the JWT\n * claims server-side). `method` is POST for create, PATCH for transition/update/\n * deactivate.\n */\nasync function adminEmbed<T>(\n method: \"POST\" | \"PATCH\",\n url: string,\n accessToken: string,\n body: unknown,\n signal?: AbortSignal,\n): Promise<T> {\n const res = await fetch(url, {\n method,\n headers: { Authorization: `Bearer ${accessToken}`, \"Content-Type\": \"application/json\" },\n body: JSON.stringify(body),\n signal,\n });\n if (!res.ok) throw await parseError(res);\n return res.json() as Promise<T>;\n}\n\nexport class PlayaOSClient {\n private readonly baseUrl: string;\n private readonly apiKey: string;\n\n constructor(opts: ClientOptions) {\n this.baseUrl = opts.baseUrl.replace(/\\/$/, \"\");\n this.apiKey = opts.apiKey;\n }\n\n readonly members = {\n list: (params?: { role?: MemberRole; status?: string }, opts?: { signal?: AbortSignal }): Promise<Member[]> =>\n request(buildUrl(this.baseUrl, \"/members\", params), this.apiKey, opts?.signal),\n\n get: (id: string, opts?: { signal?: AbortSignal }): Promise<Member> =>\n request(buildUrl(this.baseUrl, `/members/${encodeURIComponent(id)}`), this.apiKey, opts?.signal),\n };\n\n readonly applications = {\n list: (\n params?: { status?: ApplicationStatus; year?: number },\n opts?: { signal?: AbortSignal },\n ): Promise<Application[]> => request(buildUrl(this.baseUrl, \"/applications\", params), this.apiKey, opts?.signal),\n\n // Anonymous external-camp submission against POST /api/embed/v1/applications\n // (PLA-481). Uses the X-Camp-Key header rather than Bearer auth. Pass\n // `accessToken` to additionally send a PlayaOS IdP JWT (PLA-573 path) so\n // the server trusts the verified profile claim and skips the email-based\n // bootstrap.\n create: (\n payload: ApplicationCreatePayload,\n opts?: { accessToken?: string; signal?: AbortSignal },\n ): Promise<ApplicationCreateResponse> =>\n postEmbed(buildEmbedUrl(this.baseUrl, \"/applications\"), this.apiKey, payload, opts),\n };\n\n readonly dues = {\n list: (params?: { userId?: string; year?: number }, opts?: { signal?: AbortSignal }): Promise<DuesStatus[]> =>\n request(buildUrl(this.baseUrl, \"/dues\", params), this.apiKey, opts?.signal),\n };\n\n readonly shifts = {\n list: (\n params?: { year?: number; fromDate?: string; toDate?: string; publishedOnly?: boolean },\n opts?: { signal?: AbortSignal },\n ): Promise<Shift[]> => request(buildUrl(this.baseUrl, \"/shifts\", params), this.apiKey, opts?.signal),\n };\n\n readonly org = {\n get: (opts?: { signal?: AbortSignal }): Promise<OrgConfig> =>\n request(buildUrl(this.baseUrl, \"/org\"), this.apiKey, opts?.signal),\n };\n\n readonly payments = {\n page: (\n memberId: string,\n duesCollectionId?: string,\n opts?: { signal?: AbortSignal },\n ): Promise<PaymentPageResponse> =>\n post(buildUrl(this.baseUrl, \"/payments/page\"), this.apiKey, { memberId, duesCollectionId }, opts?.signal),\n };\n\n // Admin write APIs (PLA-489). Each method requires `accessToken` — a Supabase\n // JWT for a camp admin/super_admin. The org boundary is enforced server-side\n // from the JWT claims; these endpoints do not use the X-Camp-Key.\n readonly admin = {\n applications: {\n transition: (\n id: string,\n payload: ApplicationTransitionPayload,\n opts: { accessToken: string; signal?: AbortSignal },\n ): Promise<ApplicationTransitionResponse> =>\n adminEmbed(\n \"PATCH\",\n buildEmbedUrl(this.baseUrl, `/admin/applications/${encodeURIComponent(id)}`),\n opts.accessToken,\n payload,\n opts.signal,\n ),\n },\n members: {\n create: (\n payload: MemberCreatePayload,\n opts: { accessToken: string; signal?: AbortSignal },\n ): Promise<MemberCreateResponse> =>\n adminEmbed(\"POST\", buildEmbedUrl(this.baseUrl, \"/admin/members\"), opts.accessToken, payload, opts.signal),\n\n update: (\n id: string,\n patch: MemberUpdatePayload,\n opts: { accessToken: string; signal?: AbortSignal },\n ): Promise<MemberUpdateResponse> =>\n adminEmbed(\n \"PATCH\",\n buildEmbedUrl(this.baseUrl, `/admin/members/${encodeURIComponent(id)}`),\n opts.accessToken,\n patch,\n opts.signal,\n ),\n\n deactivate: (\n id: string,\n opts: { accessToken: string; signal?: AbortSignal },\n ): Promise<MemberDeactivateResponse> =>\n adminEmbed(\n \"PATCH\",\n buildEmbedUrl(this.baseUrl, `/admin/members/${encodeURIComponent(id)}`),\n opts.accessToken,\n { status: \"inactive\" },\n opts.signal,\n ),\n },\n };\n}\n\n/** Convenience factory — equivalent to `new PlayaOSClient(opts)`. */\nexport function createClient(opts: ClientOptions): PlayaOSClient {\n return new PlayaOSClient(opts);\n}\n","/**\n * Server-side OAuth-code → ID-token exchange (PLA-573 T24).\n *\n * Used by camp sites' confidential-client backends to swap a one-time\n * authorization code from the PlayaOS IdP for a short-lived ID token.\n * Public/PKCE-only clients exchange directly from the browser via\n * `usePlayaOSAuth` in `@playaos/react`; this helper exists for confidential\n * clients that need to send the client_secret server-side.\n */\n\nexport interface ExchangeParams {\n authBaseUrl?: string;\n code: string;\n codeVerifier: string;\n clientId: string;\n clientSecret: string;\n redirectUri: string;\n}\n\nexport interface ExchangeResult {\n idToken: string;\n expiresAt: string;\n refreshToken: string;\n}\n\nexport async function exchangeCode(params: ExchangeParams): Promise<ExchangeResult> {\n const base = params.authBaseUrl ?? \"https://auth.playaos.app\";\n const res = await fetch(`${base}/api/auth/v1/exchange`, {\n method: \"POST\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({\n code: params.code,\n code_verifier: params.codeVerifier,\n client_id: params.clientId,\n client_secret: params.clientSecret,\n redirect_uri: params.redirectUri,\n }),\n });\n let body: unknown;\n try {\n body = await res.json();\n } catch {\n throw new Error(`Exchange failed: non-JSON response (${res.status})`);\n }\n\n if (!res.ok) {\n const errorReason =\n typeof body === \"object\" && body !== null && \"error\" in body && typeof body.error === \"string\"\n ? body.error\n : String(res.status);\n throw new Error(`Exchange failed: ${errorReason}`);\n }\n\n if (\n typeof body !== \"object\" ||\n body === null ||\n !(\"idToken\" in body) ||\n typeof body.idToken !== \"string\" ||\n !(\"expiresAt\" in body) ||\n typeof body.expiresAt !== \"string\"\n ) {\n throw new Error(\"Exchange failed: malformed success response\");\n }\n\n // refreshToken is optional in the response (public clients don't get one);\n // surface as undefined when absent. Result type's refreshToken is required\n // because exchangeCode is the confidential-client helper — keep it required\n // in the typed return but pull it defensively in case the server omits it.\n const refreshToken = \"refreshToken\" in body && typeof body.refreshToken === \"string\" ? body.refreshToken : undefined;\n\n if (refreshToken === undefined) {\n throw new Error(\"Exchange failed: missing refreshToken in confidential response\");\n }\n\n return {\n idToken: body.idToken,\n expiresAt: body.expiresAt,\n refreshToken,\n };\n}\n"],"mappings":"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","names":[]}
|
package/package.json
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@playaos/api-client",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Typed API client for PlayaOS — manage camp members, dues, shifts, and applications",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./schemas": {
|
|
14
|
+
"types": "./dist/applications-schema.d.ts",
|
|
15
|
+
"import": "./dist/applications-schema.js"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"files": ["dist", "README.md"],
|
|
19
|
+
"scripts": {
|
|
20
|
+
"build": "tsup",
|
|
21
|
+
"dev": "tsup --watch",
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"test": "vitest run"
|
|
24
|
+
},
|
|
25
|
+
"keywords": ["playaos", "burning-man", "camp-management", "api-client"],
|
|
26
|
+
"license": "MIT",
|
|
27
|
+
"peerDependencies": {
|
|
28
|
+
"zod": "^4.4.3"
|
|
29
|
+
},
|
|
30
|
+
"peerDependenciesMeta": {
|
|
31
|
+
"zod": {
|
|
32
|
+
"optional": true
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
"devDependencies": {
|
|
36
|
+
"tsup": "^8.5.1",
|
|
37
|
+
"typescript": "^5.9.3",
|
|
38
|
+
"vitest": "^4.1.5",
|
|
39
|
+
"zod": "^4.4.3"
|
|
40
|
+
}
|
|
41
|
+
}
|