@spoketech/hub-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 ADDED
@@ -0,0 +1,252 @@
1
+ # `@spoketech/hub-client`
2
+
3
+ Async-first npm client for the Spoke Hub API, with optional Effect-native APIs underneath.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install @spoketech/hub-client
9
+ ```
10
+
11
+ ## Client Shapes
12
+
13
+ The package exposes a friendly SDK surface plus the generated API client:
14
+
15
+ - `makePromiseSpokeClient`: Recommended object-style SDK for normal app and integration code.
16
+ - `makePromiseHubApiClient` / `makePromiseServiceHubApiClient`: Generated Promise client that mirrors HTTP routes.
17
+ - `makeHubApiClient` / `makeServiceHubApiClient`: Raw Effect clients when your app already uses Effect.
18
+
19
+ The generated client takes a single request object. Depending on the endpoint, that object can contain:
20
+
21
+ - `path`: Route parameters like `organizationId`, `memberId`, `guestId`, `runId`, or `serviceClientId`.
22
+ - `payload`: JSON request body.
23
+ - `urlParams`: Query-string parameters.
24
+ - `headers`: Per-call headers. Most callers should set auth at client creation time instead.
25
+
26
+ ## Recommended Usage
27
+
28
+ ```ts
29
+ import { makePromiseSpokeClient } from "@spoketech/hub-client";
30
+
31
+ const { client } = await makePromiseSpokeClient({
32
+ baseUrl: "https://hub.example.com",
33
+ clientId: process.env.SPOKE_CLIENT_ID!,
34
+ privateKey: process.env.SPOKE_PRIVATE_KEY!,
35
+ });
36
+
37
+ const members = await client.members.list({
38
+ organizationId: "org_123",
39
+ responseMode: "med",
40
+ page: 1,
41
+ pageSize: 25,
42
+ });
43
+
44
+ const member = await client.members.get({
45
+ organizationId: "org_123",
46
+ memberId: "mem_123",
47
+ responseMode: "full",
48
+ });
49
+
50
+ const guests = await client.guests.list({
51
+ organizationId: "org_123",
52
+ name: "Jane",
53
+ });
54
+ ```
55
+
56
+ ## Live Onsite Resource Calls
57
+
58
+ For member-source and voucher operations that must run from the venue network, use the onsite resource wrapper. It dispatches the command and waits briefly for the onsite client to complete it. If the onsite client takes longer than `waitMs`, the response includes the accepted command so callers can continue polling or observe webhooks.
59
+
60
+ ```ts
61
+ import { makePromiseOnsiteResourceClient } from "@spoketech/hub-client";
62
+
63
+ const { client } = await makePromiseOnsiteResourceClient({
64
+ baseUrl: "https://hub.example.com",
65
+ clientId: process.env.SPOKE_CLIENT_ID!,
66
+ privateKey: process.env.SPOKE_PRIVATE_KEY!,
67
+ });
68
+
69
+ const lookup = await client.members.lookup({
70
+ organizationId: "org_123",
71
+ lookupBy: "member_id",
72
+ lookupValue: "12345",
73
+ waitMs: 10_000,
74
+ });
75
+
76
+ if (lookup.settled && lookup.command.status === "completed") {
77
+ console.log(lookup.command.result);
78
+ }
79
+
80
+ const voucher = await client.vouchers.issue({
81
+ organizationId: "org_123",
82
+ waitMs: 10_000,
83
+ payload: {
84
+ memberId: "mem_123",
85
+ rewardId: "reward_456",
86
+ quantity: 1,
87
+ },
88
+ });
89
+ ```
90
+
91
+ The full Spoke client exposes the same live onsite wrapper under `client.onsite`:
92
+
93
+ ```ts
94
+ const { client } = await makePromiseSpokeClient(options);
95
+
96
+ const lookup = await client.onsite.members.lookup({
97
+ organizationId: "org_123",
98
+ lookupBy: "member_id",
99
+ lookupValue: "12345",
100
+ waitMs: 10_000,
101
+ });
102
+ ```
103
+
104
+ ## Service Client Authentication
105
+
106
+ ```ts
107
+ import { makePromiseServiceHubApiClient } from "@spoketech/hub-client";
108
+
109
+ const { client } = await makePromiseServiceHubApiClient({
110
+ baseUrl: "https://hub.example.com",
111
+ clientId: process.env.SPOKE_CLIENT_ID!,
112
+ privateKey: process.env.SPOKE_PRIVATE_KEY!,
113
+ });
114
+
115
+ const organizations = await client.organizations.listOrganizations({
116
+ urlParams: { page: "1", pageSize: "25", name: "Spoke" },
117
+ });
118
+ ```
119
+
120
+ `baseUrl` should usually be the site origin. The client appends `/api` by default, so requests target `/api/v1/...`. Override `apiBasePath` if your deployment exposes the API somewhere else.
121
+
122
+ `makePromiseServiceHubApiClient` returns the generated low-level client. It mirrors HTTP routes with `path`, `payload`, and `urlParams`. Prefer `makePromiseSpokeClient` for app and integration code; it keeps object-style inputs while still returning `hubClient` as a raw escape hatch.
123
+
124
+ ## Included Skill
125
+
126
+ The npm package also ships a Codex skill at `skills/spoke-hub-client/`. If the package is installed in `node_modules`, the skill lives at:
127
+
128
+ ```text
129
+ node_modules/@spoketech/hub-client/skills/spoke-hub-client
130
+ ```
131
+
132
+ ## Effect Usage
133
+
134
+ If you do want the raw Effect client, `makeHubApiClient` and `makeServiceHubApiClient` are still exported.
135
+
136
+ ## Onsite Source Writes
137
+
138
+ For Odyssey Gaming sources, member creation, update, renewal, lookup, and onsite voucher issuing are command requests, not direct Hub-to-Nexus/SENPOS API calls.
139
+
140
+ - The Hub publishes a command to the configured onsite agent through AWS IoT Core.
141
+ - The onsite agent runs on the venue network, calls Nexus from the onsite IP, and then posts the result back to Hub through the completion endpoints.
142
+ - The `onsiteCalls.callOnsiteResource` endpoint and onsite resource wrapper wait briefly for completion, then return the accepted command if the onsite client is still working.
143
+
144
+ Relevant configuration:
145
+
146
+ - Hub: `AWS_IOT_DATA_ENDPOINT`, `AWS_REGION`, `ONSITE_AGENT_IOT_TOPIC_PREFIX`
147
+ - Onsite agent: `ONSITE_AGENT_TRANSPORT=iot`, `ONSITE_AGENT_IOT_TOPIC_PREFIX`
148
+
149
+ ## Endpoint Reference
150
+
151
+ All routes below are relative to `/api/v1`. The promise client and the Effect client use the same method names.
152
+
153
+ ### Auth
154
+
155
+ | Client method | Route | Request | Returns |
156
+ | --- | --- | --- | --- |
157
+ | `client.auth.getAdminSession({})` | `GET /admin/session` | none | `AdminSession` |
158
+
159
+ ### Organizations
160
+
161
+ | Client method | Route | Request | Returns |
162
+ | --- | --- | --- | --- |
163
+ | `client.organizations.listOrganizations({ urlParams })` | `GET /organizations` | optional `page`, `pageSize`, `name` | `PaginatedOrganizations` |
164
+ | `client.organizations.createOrganization({ payload })` | `POST /organizations` | `CreateOrganizationInput` | `Organization` |
165
+ | `client.organizations.updateOrganization({ path, payload })` | `PATCH /organizations/:organizationId` | `path.organizationId`, `UpdateOrganizationInput` | `Organization` |
166
+ | `client.organizations.deleteOrganization({ path, payload })` | `DELETE /organizations/:organizationId` | `path.organizationId`, `DeletePayload` | `void` |
167
+
168
+ ### Members
169
+
170
+ | Client method | Route | Request | Returns |
171
+ | --- | --- | --- | --- |
172
+ | `client.members.listOrganizationMembers({ path, urlParams })` | `GET /organizations/:organizationId/members` | `path.organizationId`, optional paging and search `urlParams` | `OrganizationMembersListResponse` |
173
+ | `client.members.getOrganizationMember({ path, urlParams })` | `GET /organizations/:organizationId/members/:memberId` | `path.organizationId`, `path.memberId`, optional `responseMode` | `MemberResponse` |
174
+ | `client.members.listOrganizationMemberMembershipTypes({ path, urlParams })` | `GET /organizations/:organizationId/members/membership-types` | `path.organizationId`, optional `page`, `pageSize` | `PaginatedOrganizationMemberMembershipTypes` |
175
+ | `client.members.listOrganizationMemberRatingGrades({ path, urlParams })` | `GET /organizations/:organizationId/members/rating-grades` | `path.organizationId`, optional `page`, `pageSize` | `PaginatedOrganizationMemberRatingGrades` |
176
+ | `client.members.createOrganizationMember({ path, payload })` | `POST /organizations/:organizationId/members` | `path.organizationId`, `CreateMemberInput` for local-member creation | `Member` |
177
+ | `client.members.requestSourceManagedMemberCreate({ path, payload })` | `POST /organizations/:organizationId/members/source-create` | `path.organizationId`, `RequestSourceManagedMemberCreateInput` for Odyssey/onsite source creation | `OrganizationMemberSourceCommand` |
178
+ | `client.members.requestSourceManagedMemberRenewal({ path, payload })` | `POST /organizations/:organizationId/members/:memberId/source-renewal` | `path.organizationId`, `path.memberId`, `RequestSourceManagedMemberRenewalInput` for Odyssey/onsite source renewal | `OrganizationMemberSourceCommand` |
179
+ | `client.members.updateOrganizationMember({ path, payload })` | `PATCH /organizations/:organizationId/members/:memberId` | `path.organizationId`, `path.memberId`, `UpdateMemberInput` | `Member` |
180
+ | `client.members.deleteOrganizationMember({ path, payload })` | `DELETE /organizations/:organizationId/members/:memberId` | `path.organizationId`, `path.memberId`, `DeletePayload` | `void` |
181
+
182
+ ### Member Sources
183
+
184
+ | Client method | Route | Request | Returns |
185
+ | --- | --- | --- | --- |
186
+ | `client.memberSources.getOrganizationMemberSource({ path })` | `GET /organizations/:organizationId/member-source` | `path.organizationId` | `OrganizationMemberSource \| null` |
187
+ | `client.memberSources.upsertOrganizationMemberSource({ path, payload })` | `PUT /organizations/:organizationId/member-source` | `path.organizationId`, `UpsertOrganizationMemberSourceInput` | `OrganizationMemberSource` |
188
+ | `client.memberSources.bootstrapOrganizationMemberSourceAgent({ path, payload })` | `POST /organizations/:organizationId/member-source/agent/bootstrap` | `path.organizationId`, `BootstrapOrganizationMemberSourceAgentInput` | `BootstrapOrganizationMemberSourceAgentResponse` |
189
+ | `client.memberSources.getOrganizationMemberSourceCsvUploadReadiness({ path })` | `GET /organizations/:organizationId/member-source/csv-upload-readiness` | `path.organizationId` | `CsvUploadReadiness` |
190
+ | `client.memberSources.getOrganizationMemberSourceCsvMapping({ path })` | `GET /organizations/:organizationId/member-source/csv-mapping` | `path.organizationId` | `OrganizationMemberSourceCsvMapping \| null` |
191
+ | `client.memberSources.upsertOrganizationMemberSourceCsvMapping({ path, payload })` | `PUT /organizations/:organizationId/member-source/csv-mapping` | `path.organizationId`, `UpsertOrganizationMemberSourceCsvMappingInput` | `OrganizationMemberSourceCsvMapping` |
192
+ | `client.memberSources.createOrganizationMemberSourceCsvUpload({ path, payload })` | `POST /organizations/:organizationId/member-source/csv-uploads` | `path.organizationId`, `CreateOrganizationMemberSourceCsvUploadInput` | `CreateOrganizationMemberSourceCsvUploadResponse` |
193
+ | `client.memberSources.listOrganizationMemberSyncRuns({ path, urlParams })` | `GET /organizations/:organizationId/member-source/runs` | `path.organizationId`, optional `page`, `pageSize` | `PaginatedOrganizationMemberSyncRuns` |
194
+ | `client.memberSources.listOrganizationMemberSourceCommands({ path, urlParams })` | `GET /organizations/:organizationId/member-source/commands` | `path.organizationId`, optional `page`, `pageSize` | `PaginatedOrganizationMemberSourceCommands` |
195
+ | `client.memberSources.triggerOrganizationMemberSync({ path, payload })` | `POST /organizations/:organizationId/member-source/sync` | `path.organizationId`, `TriggerOrganizationMemberSyncInput` | `OrganizationMemberSyncRun` |
196
+
197
+ ### Vouchers
198
+
199
+ | Client method | Route | Request | Returns |
200
+ | --- | --- | --- | --- |
201
+ | `client.vouchers.getOrganizationVoucherProvider({ path })` | `GET /organizations/:organizationId/voucher-provider` | `path.organizationId` | `OrganizationVoucherProvider \| null` |
202
+ | `client.vouchers.upsertOrganizationVoucherProvider({ path, payload })` | `PUT /organizations/:organizationId/voucher-provider` | `path.organizationId`, `UpsertOrganizationVoucherProviderInput` | `OrganizationVoucherProvider` |
203
+ | `client.vouchers.requestVoucherIssue({ path, payload })` | `POST /organizations/:organizationId/vouchers/issue` | `path.organizationId`, `RequestVoucherIssueInput` | `OrganizationVoucherCommand` |
204
+ | `client.vouchers.pollOnsiteVoucherCommand({ path })` | `POST /organizations/:organizationId/onsite-voucher-commands/poll` | `path.organizationId` | `OnsiteVoucherCommand \| null` |
205
+ | `client.vouchers.completeOnsiteVoucherCommand({ path, payload })` | `POST /organizations/:organizationId/onsite-voucher-commands/:commandId/complete` | `path.organizationId`, `path.commandId`, `CompleteOnsiteVoucherCommandInput` | `OrganizationVoucherCommand` |
206
+
207
+ ### Onsite Calls
208
+
209
+ | Client method | Route | Request | Returns |
210
+ | --- | --- | --- | --- |
211
+ | `client.onsiteCalls.callOnsiteResource({ path, payload })` | `POST /organizations/:organizationId/onsite-calls` | `OnsiteResourceCallRequest` for member create/update/renew/lookup or voucher issue | `OnsiteResourceCallResponse` |
212
+
213
+ ### Onsite Agents
214
+
215
+ | Client method | Route | Request | Returns |
216
+ | --- | --- | --- | --- |
217
+ | `client.onsiteAgents.heartbeatOnsiteAgent({ payload })` | `POST /onsite-agent/heartbeat` | `OnsiteAgentHeartbeatInput` | `OrganizationMemberSourceAgent` |
218
+ | `client.onsiteAgents.pollOnsiteAgentCommand({})` | `POST /onsite-agent/commands/poll` | none | `OnsiteAgentCommand \| null` |
219
+ | `client.onsiteAgents.pollOnsiteAgentMemberCreateCommand({})` | `POST /onsite-agent/member-commands/poll` | none | `OnsiteAgentMemberCreateCommand \| null` |
220
+ | `client.onsiteAgents.pollOnsiteAgentMemberRenewalCommand({})` | `POST /onsite-agent/member-renewal-commands/poll` | none | `OnsiteAgentMemberRenewalCommand \| null` |
221
+ | `client.onsiteAgents.completeOnsiteAgentRun({ path, payload })` | `POST /onsite-agent/runs/:runId/complete` | `path.runId`, `OnsiteAgentCompleteRunInput` | `OrganizationMemberSyncRun` |
222
+ | `client.onsiteAgents.completeOnsiteAgentMemberCreate({ path, payload })` | `POST /onsite-agent/member-commands/:commandId/complete` | `path.commandId`, `OnsiteAgentCompleteMemberCreateInput` | `OrganizationMemberSourceCommand` |
223
+ | `client.onsiteAgents.completeOnsiteAgentMemberRenewal({ path, payload })` | `POST /onsite-agent/member-renewal-commands/:commandId/complete` | `path.commandId`, `OnsiteAgentCompleteMemberRenewalInput` | `OrganizationMemberSourceCommand` |
224
+
225
+ ### Guests
226
+
227
+ | Client method | Route | Request | Returns |
228
+ | --- | --- | --- | --- |
229
+ | `client.guests.listGuests({ path, urlParams })` | `GET /organizations/:organizationId/guests` | `path.organizationId`, optional paging and filters `urlParams` | `PaginatedGuests` |
230
+ | `client.guests.createGuest({ path, payload })` | `POST /organizations/:organizationId/guests` | `path.organizationId`, `CreateGuestInput` | `Guest` |
231
+ | `client.guests.updateGuest({ path, payload })` | `PATCH /organizations/:organizationId/guests/:guestId` | `path.organizationId`, `path.guestId`, `UpdateGuestInput` | `Guest` |
232
+ | `client.guests.deleteGuest({ path, payload })` | `DELETE /organizations/:organizationId/guests/:guestId` | `path.organizationId`, `path.guestId`, `DeletePayload` | `void` |
233
+
234
+ ### Service Clients
235
+
236
+ | Client method | Route | Request | Returns |
237
+ | --- | --- | --- | --- |
238
+ | `client.serviceClients.listServiceClients({ urlParams })` | `GET /service-clients` | optional `page`, `pageSize` | `PaginatedServiceClients` |
239
+ | `client.serviceClients.createServiceClient({ payload })` | `POST /service-clients` | `CreateServiceClientInput` | `{ client: ServiceClient; privateKey: string }` |
240
+ | `client.serviceClients.updateServiceClient({ path, payload })` | `PATCH /service-clients/:serviceClientId` | `path.serviceClientId`, `UpdateServiceClientInput` | `{ client: ServiceClient; privateKey?: string }` |
241
+
242
+ ## Type Exports
243
+
244
+ The package re-exports the shared contract types, so you can import payload and response types directly:
245
+
246
+ ```ts
247
+ import type {
248
+ CreateMemberInput,
249
+ MemberResponse,
250
+ OrganizationMembersListResponse,
251
+ } from "@spoketech/hub-client";
252
+ ```