@productcraft/social 0.7.1 → 1.0.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 CHANGED
@@ -14,8 +14,8 @@ Social's surface splits two ways — both reach the same data but differ in auth
14
14
 
15
15
  | Caller | URL prefix | Auth | What you do |
16
16
  |---|---|---|---|
17
- | **PlatformUser admin** | `/v1/workspaces/{workspaceId}/...` | cookie or PAK | Create communities, run moderation, read analytics. |
18
- | **Customer backend** | `/v1/communities/{communityId}/...` | PAK + `X-Acting-As: <actor_id>` header | Day-to-day reads/writes on behalf of an EndUser. |
17
+ | **PlatformUser admin** | `/v1/workspaces/{workspace_id}/...` | cookie or PAK | Create communities, run moderation, read analytics. |
18
+ | **Customer backend** | `/v1/communities/{community_id}/...` | PAK + `X-Acting-As: <actor_id>` header | Day-to-day reads/writes on behalf of an EndUser. |
19
19
 
20
20
  The customer backend acts on behalf of one of its EndUsers via the `X-Acting-As` header — the SDK passes whatever you put in headers straight through, so a per-request wrapper that injects the acting actor id is idiomatic.
21
21
 
@@ -24,14 +24,13 @@ The customer backend acts on behalf of one of its EndUsers via the `X-Acting-As`
24
24
  ```ts
25
25
  import { Social } from "@productcraft/social";
26
26
 
27
- const social = new Social({
28
- auth: { type: "apiKey", key: process.env.PCFT_KEY! },
29
- });
27
+ // Reads PRODUCTCRAFT_API_KEY from the environment
28
+ const social = new Social();
30
29
 
31
30
  const { data, error } = await social.client.POST(
32
- "/v1/workspaces/{workspaceId}/communities",
31
+ "/v1/workspaces/{workspace_id}/communities",
33
32
  {
34
- params: { path: { workspaceId: "<workspace-uuid>" } },
33
+ params: { path: { workspace_id: "<workspace-uuid>" } },
35
34
  body: { display_name: "Founders Club", slug: "founders" },
36
35
  },
37
36
  );
@@ -43,10 +42,10 @@ const { data, error } = await social.client.POST(
43
42
  const actor_id = "<actor-uuid>"; // the EndUser's Social actor id
44
43
 
45
44
  const { data, error } = await social.client.POST(
46
- "/v1/communities/{communityId}/posts",
45
+ "/v1/communities/{community_id}/posts",
47
46
  {
48
47
  params: {
49
- path: { communityId: "<community-uuid>" },
48
+ path: { community_id: "<community-uuid>" },
50
49
  header: { "X-Acting-As": actor_id },
51
50
  },
52
51
  body: {
@@ -63,10 +62,42 @@ const { data, error } = await social.client.POST(
63
62
 
64
63
  Social's wire is snake_case both at the DTO level and in TS types — there's no name translation layer for this surface. Use snake_case keys in `body` to match what the API actually accepts.
65
64
 
65
+ ## Migrating from 0.x to 1.0
66
+
67
+ 1.0 renames every **path parameter** to snake_case, to match the rest of the ProductCraft APIs: `{communityId}` is now `{community_id}`, `{actorId}` is `{actor_id}`, `{srcActorId}` is `{src_actor_id}`, and so on. Request URLs are unchanged, so nothing breaks at runtime; the change is in the TypeScript path keys and in `params.path`:
68
+
69
+ ```ts
70
+ // 0.x
71
+ await social.client.GET("/v1/communities/{communityId}/posts/{postId}", {
72
+ params: { path: { communityId, postId } },
73
+ });
74
+
75
+ // 1.0
76
+ await social.client.GET("/v1/communities/{community_id}/posts/{post_id}", {
77
+ params: { path: { community_id: communityId, post_id: postId } },
78
+ });
79
+ ```
80
+
81
+ Where a route names a target actor next to the acting `actor_id`, the target got its own name: `posts/{post_id}/tags/{tagged_actor_id}/approve`, `actors/{tagged_actor_id}/tagged`, `actors/{owner_actor_id}/highlights`, `lists/{list_id}/members/{member_actor_id}`, `conversations/{conversation_id}/members/{member_actor_id}`. A search-and-replace of the placeholders plus the `path` keys is the whole migration. Post `attributes` now read back with the keys exactly as you stored them (`imageUrl` stays `imageUrl`); before, the API returned them snake_cased.
82
+
83
+ ## Credentials from the environment
84
+
85
+ Server-side, omit `auth` and the client reads your workspace key from the environment:
86
+
87
+ ```bash
88
+ # .env
89
+ PRODUCTCRAFT_API_KEY=pcft_live_...
90
+ ```
91
+
92
+ - An explicit `auth` always wins over the environment.
93
+ - Hosts are never read from the environment. Override them in code with `baseUrl`.
94
+ - This is a server-only fallback. Never put a key in `NEXT_PUBLIC_*` or any other variable a bundler inlines into browser code.
95
+
66
96
  ## Configuration
67
97
 
68
98
  ```ts
69
99
  new Social({
100
+ // Omit to use PRODUCTCRAFT_API_KEY from the environment
70
101
  auth: { type: "apiKey", key: "pcft_live_..." }
71
102
  | { type: "bearer", token: "eyJ..." }
72
103
  | { type: "cookie", value: "auth_token=..." },
@@ -82,20 +113,20 @@ new Social({
82
113
  ```ts
83
114
  // Create a post
84
115
  await social.client.POST(
85
- "/v1/communities/{communityId}/posts",
86
- { params: { path: { communityId } }, body: { text: "..." } },
116
+ "/v1/communities/{community_id}/posts",
117
+ { params: { path: { community_id: communityId } }, body: { text: "..." } },
87
118
  );
88
119
 
89
120
  // Get the actor's ranked home feed
90
121
  await social.client.GET(
91
- "/v1/communities/{communityId}/actors/{actorId}/feed",
92
- { params: { path: { communityId, actorId } } },
122
+ "/v1/communities/{community_id}/actors/{actor_id}/feed",
123
+ { params: { path: { community_id: communityId, actor_id: actorId } } },
93
124
  );
94
125
 
95
126
  // Discover (community-wide ranked feed, not personalised to follows)
96
127
  await social.client.GET(
97
- "/v1/communities/{communityId}/discover-feed",
98
- { params: { path: { communityId } } },
128
+ "/v1/communities/{community_id}/discover-feed",
129
+ { params: { path: { community_id: communityId } } },
99
130
  );
100
131
 
101
132
  // React / unreact / bookmark / quote / repost — same shape, different paths
@@ -106,13 +137,13 @@ await social.client.GET(
106
137
  ```ts
107
138
  // Post a story (24h TTL)
108
139
  await social.client.POST(
109
- "/v1/communities/{communityId}/stories",
140
+ "/v1/communities/{community_id}/stories",
110
141
  { params: { ... }, body: { media_url: "...", visibility: "close_friends" } },
111
142
  );
112
143
 
113
144
  // Story tray (the bubble row at the top of the home screen)
114
145
  await social.client.GET(
115
- "/v1/communities/{communityId}/actors/{actorId}/story-tray",
146
+ "/v1/communities/{community_id}/actors/{actor_id}/story-tray",
116
147
  { ... },
117
148
  );
118
149
  ```
@@ -122,12 +153,12 @@ await social.client.GET(
122
153
  ```ts
123
154
  // Follow / unfollow
124
155
  await social.client.PUT(
125
- "/v1/communities/{communityId}/follows/{srcActorId}/{dstActorId}",
156
+ "/v1/communities/{community_id}/follows/{src_actor_id}/{dst_actor_id}",
126
157
  { ... },
127
158
  );
128
159
 
129
160
  // Block / mute / restrict — same shape, different paths
130
- // Close friends, hashtag follows, muted terms — all under /actors/{actorId}/...
161
+ // Close friends, hashtag follows, muted terms — all under /actors/{actor_id}/...
131
162
  ```
132
163
 
133
164
  ### Notifications
@@ -135,19 +166,19 @@ await social.client.PUT(
135
166
  ```ts
136
167
  // List
137
168
  await social.client.GET(
138
- "/v1/communities/{communityId}/actors/{actorId}/notifications",
169
+ "/v1/communities/{community_id}/actors/{actor_id}/notifications",
139
170
  { ... },
140
171
  );
141
172
 
142
173
  // Unread count for the bell badge
143
174
  await social.client.GET(
144
- "/v1/communities/{communityId}/actors/{actorId}/notifications/unread-count",
175
+ "/v1/communities/{community_id}/actors/{actor_id}/notifications/unread-count",
145
176
  { ... },
146
177
  );
147
178
 
148
179
  // Mark all read
149
180
  await social.client.POST(
150
- "/v1/communities/{communityId}/actors/{actorId}/notifications/read-all",
181
+ "/v1/communities/{community_id}/actors/{actor_id}/notifications/read-all",
151
182
  { ... },
152
183
  );
153
184
  ```
@@ -157,13 +188,13 @@ await social.client.POST(
157
188
  ```ts
158
189
  // Start / open a conversation
159
190
  await social.client.POST(
160
- "/v1/communities/{communityId}/conversations",
191
+ "/v1/communities/{community_id}/conversations",
161
192
  { params: { ... }, body: { members: ["act_a", "act_b"] } },
162
193
  );
163
194
 
164
195
  // Send a message
165
196
  await social.client.POST(
166
- "/v1/communities/{communityId}/conversations/{conversationId}/messages",
197
+ "/v1/communities/{community_id}/conversations/{conversation_id}/messages",
167
198
  { ... },
168
199
  );
169
200
  ```
@@ -173,19 +204,19 @@ await social.client.POST(
173
204
  ```ts
174
205
  // List flags
175
206
  await social.client.GET(
176
- "/v1/workspaces/{workspaceId}/communities/{communityId}/moderation/flags",
207
+ "/v1/workspaces/{workspace_id}/communities/{community_id}/moderation/flags",
177
208
  { ... },
178
209
  );
179
210
 
180
211
  // Act on a flag (resolve / dismiss / take action on the target)
181
212
  await social.client.POST(
182
- "/v1/workspaces/{workspaceId}/communities/{communityId}/moderation/flags/{flagId}/actions",
213
+ "/v1/workspaces/{workspace_id}/communities/{community_id}/moderation/flags/{flag_id}/actions",
183
214
  { params: { ... }, body: { action: "remove_content", note: "..." } },
184
215
  );
185
216
 
186
217
  // Shadow-ban an actor
187
218
  await social.client.PUT(
188
- "/v1/workspaces/{workspaceId}/communities/{communityId}/moderation/actors/{actorId}/shadow-ban",
219
+ "/v1/workspaces/{workspace_id}/communities/{community_id}/moderation/actors/{actor_id}/shadow-ban",
189
220
  { ... },
190
221
  );
191
222
  ```