@antiphony/shared 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.
Files changed (71) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +50 -0
  3. package/dist/cjs/api-codecs.cjs +971 -0
  4. package/dist/cjs/api-codecs.d.cts +542 -0
  5. package/dist/cjs/errors/index.cjs +56 -0
  6. package/dist/cjs/errors/index.d.cts +45 -0
  7. package/dist/cjs/index.cjs +1276 -0
  8. package/dist/cjs/index.d.cts +11 -0
  9. package/dist/cjs/nsid.cjs +37 -0
  10. package/dist/cjs/nsid.d.cts +42 -0
  11. package/dist/cjs/observability/index.cjs +58 -0
  12. package/dist/cjs/observability/index.d.cts +1 -0
  13. package/dist/cjs/observability/report-error.cjs +58 -0
  14. package/dist/cjs/observability/report-error.d.cts +48 -0
  15. package/dist/cjs/types/api.cjs +726 -0
  16. package/dist/cjs/types/api.d.cts +494 -0
  17. package/dist/cjs/types/audio.cjs +835 -0
  18. package/dist/cjs/types/audio.d.cts +1149 -0
  19. package/dist/cjs/types/blob.cjs +23 -0
  20. package/dist/cjs/types/blob.d.cts +45 -0
  21. package/dist/cjs/types/channels.cjs +170 -0
  22. package/dist/cjs/types/channels.d.cts +262 -0
  23. package/dist/cjs/types/records.cjs +407 -0
  24. package/dist/cjs/types/records.d.cts +840 -0
  25. package/dist/cjs/types/storage.cjs +414 -0
  26. package/dist/cjs/types/storage.d.cts +197 -0
  27. package/dist/cjs/types/views.cjs +820 -0
  28. package/dist/cjs/types/views.d.cts +9806 -0
  29. package/dist/cjs/utils/index.cjs +8 -0
  30. package/dist/cjs/utils/index.d.cts +7 -0
  31. package/dist/esm/api-codecs.d.ts +542 -0
  32. package/dist/esm/api-codecs.js +6 -0
  33. package/dist/esm/chunk-24KIXQZK.js +6 -0
  34. package/dist/esm/chunk-3WZJXJNU.js +378 -0
  35. package/dist/esm/chunk-5EHV73BA.js +20 -0
  36. package/dist/esm/chunk-72G3LBUQ.js +1 -0
  37. package/dist/esm/chunk-7LW2FHLD.js +48 -0
  38. package/dist/esm/chunk-7V44CPRR.js +132 -0
  39. package/dist/esm/chunk-BUNMR4ZO.js +127 -0
  40. package/dist/esm/chunk-EA4OONDV.js +380 -0
  41. package/dist/esm/chunk-F2CANZZ7.js +40 -0
  42. package/dist/esm/chunk-ORMEWXMH.js +33 -0
  43. package/dist/esm/chunk-TIZRJORN.js +24 -0
  44. package/dist/esm/chunk-XDBKR6LW.js +32 -0
  45. package/dist/esm/errors/index.d.ts +45 -0
  46. package/dist/esm/errors/index.js +2 -0
  47. package/dist/esm/index.d.ts +11 -0
  48. package/dist/esm/index.js +12 -0
  49. package/dist/esm/nsid.d.ts +42 -0
  50. package/dist/esm/nsid.js +2 -0
  51. package/dist/esm/observability/index.d.ts +1 -0
  52. package/dist/esm/observability/index.js +3 -0
  53. package/dist/esm/observability/report-error.d.ts +48 -0
  54. package/dist/esm/observability/report-error.js +2 -0
  55. package/dist/esm/types/api.d.ts +494 -0
  56. package/dist/esm/types/api.js +5 -0
  57. package/dist/esm/types/audio.d.ts +1149 -0
  58. package/dist/esm/types/audio.js +5 -0
  59. package/dist/esm/types/blob.d.ts +45 -0
  60. package/dist/esm/types/blob.js +2 -0
  61. package/dist/esm/types/channels.d.ts +262 -0
  62. package/dist/esm/types/channels.js +162 -0
  63. package/dist/esm/types/records.d.ts +840 -0
  64. package/dist/esm/types/records.js +3 -0
  65. package/dist/esm/types/storage.d.ts +197 -0
  66. package/dist/esm/types/storage.js +26 -0
  67. package/dist/esm/types/views.d.ts +9806 -0
  68. package/dist/esm/types/views.js +4 -0
  69. package/dist/esm/utils/index.d.ts +7 -0
  70. package/dist/esm/utils/index.js +2 -0
  71. package/package.json +82 -0
@@ -0,0 +1,127 @@
1
+ import { ProfileViewBasicSchema } from './chunk-EA4OONDV.js';
2
+ import { FirestoreTimestampSchema } from './chunk-3WZJXJNU.js';
3
+ import { BlobRefSchema } from './chunk-5EHV73BA.js';
4
+ import { z } from 'zod';
5
+
6
+ var StrongRefSchema = z.object({
7
+ uri: z.string().regex(/^at:\/\/.+/, "Must be an at:// URI"),
8
+ cid: z.string()
9
+ });
10
+ var ReplyRefSchema = z.object({
11
+ root: StrongRefSchema,
12
+ parent: StrongRefSchema
13
+ });
14
+ var AudioEmbedSchema = z.object({
15
+ $type: z.literal("dev.antiphony.embed.audio"),
16
+ /** The audio bytes as a content-addressed storage ref (CID/path). */
17
+ audio: BlobRefSchema,
18
+ /** Duration in MILLISECONDS (platform-wide unit; not seconds). */
19
+ durationMs: z.number().int().min(0).optional(),
20
+ /** User-authored short description (audio analogue of image alt). NOT the transcript. */
21
+ alt: z.string().max(1e4).optional(),
22
+ /** Pre-computed waveform peaks (normalized 0–100) for instant rendering. */
23
+ waveform: z.array(z.number().int().min(0).max(100)).max(1e3).optional()
24
+ });
25
+ var TranscriptSegmentSchema = z.object({
26
+ startMs: z.number().int().min(0),
27
+ endMs: z.number().int().min(0),
28
+ text: z.string()
29
+ }).refine((s) => s.endMs >= s.startMs, {
30
+ message: "endMs must be >= startMs",
31
+ path: ["endMs"]
32
+ });
33
+ var TimedTranscriptSchema = z.object({
34
+ segments: z.array(TranscriptSegmentSchema),
35
+ text: z.string().optional()
36
+ });
37
+ var AudioEmbedViewSchema = z.object({
38
+ $type: z.literal("dev.antiphony.embed.audio#view"),
39
+ url: z.string().url(),
40
+ durationMs: z.number().int().min(0).optional(),
41
+ // `alt`/`waveform` are copied from the stored embed; keep the same bounds
42
+ // so a view can never carry a larger payload than the record allows.
43
+ alt: z.string().max(1e4).optional(),
44
+ waveform: z.array(z.number().int().min(0).max(100)).max(1e3).optional(),
45
+ /** Lifted from the transcript enrichment record; absent until transcription completes. */
46
+ transcript: TimedTranscriptSchema.optional()
47
+ });
48
+ var AudioPostRecordSchema = z.object({
49
+ /** Storage id (rkey/doc id). */
50
+ id: z.string(),
51
+ // --- Tenancy + facets (storage-indexed; NOT in the lexicon) ---
52
+ /** Origin app that created this record — the multi-tenant isolation key. */
53
+ originAppId: z.string(),
54
+ /** Authoring user. A queryable facet, not the tenancy boundary. */
55
+ authorId: z.string(),
56
+ /** Optional AT Protocol identity of the author (facet). */
57
+ authorDid: z.string().optional(),
58
+ /** Optional org context (facet). */
59
+ orgId: z.string().nullable().optional(),
60
+ /** Denormalized from `reply` presence: `reply` set ⇒ 'reply', else 'prompt'. */
61
+ kind: z.enum(["prompt", "reply"]),
62
+ // --- Lexicon fields (public contract) ---
63
+ /** User-authored text (bsky-semantic). May be empty for pure-audio posts. NEVER the transcript. */
64
+ text: z.string().max(3e3),
65
+ /** Optional headline; a prompt feature, not the discriminator. */
66
+ title: z.string().max(3e3).optional(),
67
+ /** Audio (or other) attachment. Audio posts carry an `AudioEmbed`. */
68
+ embed: AudioEmbedSchema.optional(),
69
+ /** Present iff this post is a reply (StrongRef root + parent). */
70
+ reply: ReplyRefSchema.optional(),
71
+ /** BCP-47 language tags for the text. */
72
+ langs: z.array(z.string()).max(3).optional(),
73
+ /** Author-applied self-label values (content warnings). Simplified from the
74
+ * lexicon's `com.atproto.label.defs#selfLabels` to the bare value strings. */
75
+ selfLabels: z.array(z.string()).optional(),
76
+ createdAt: FirestoreTimestampSchema
77
+ }).refine(
78
+ // `kind` is denormalized from `reply` presence at write time; enforce the
79
+ // invariant so an inconsistent record can't be written or read silently.
80
+ // A reply has `reply` and no `title`; a prompt has neither a `reply`.
81
+ (r) => r.kind === "reply" ? !!r.reply && r.title === void 0 : !r.reply,
82
+ {
83
+ message: "kind must match reply presence: 'reply' \u21D2 reply set & no title; 'prompt' \u21D2 no reply",
84
+ path: ["kind"]
85
+ }
86
+ );
87
+ var TranscriptEnrichmentRecordSchema = z.object({
88
+ id: z.string(),
89
+ /** The post whose audio this transcribes. */
90
+ subject: StrongRefSchema,
91
+ transcript: TimedTranscriptSchema,
92
+ /** BCP-47 language tag of the transcript. */
93
+ lang: z.string().optional(),
94
+ /** Model/provider provenance (the generator is a pluggable port). */
95
+ model: z.string().optional(),
96
+ createdAt: FirestoreTimestampSchema
97
+ });
98
+ var ActorProfileRecordSchema = z.object({
99
+ handle: z.string().min(3).max(20).optional(),
100
+ usageIntent: z.string().max(100).optional(),
101
+ rssFeed: z.string().url().optional()
102
+ });
103
+ var ViewerStateSchema = z.object({
104
+ /** True when the authenticated caller authored this post. */
105
+ isAuthor: z.boolean().default(false)
106
+ });
107
+ var PostRecordPublicSchema = z.object({
108
+ text: z.string(),
109
+ title: z.string().optional(),
110
+ reply: ReplyRefSchema.optional(),
111
+ langs: z.array(z.string()).optional(),
112
+ selfLabels: z.array(z.string()).optional(),
113
+ createdAt: FirestoreTimestampSchema
114
+ });
115
+ var AudioPostViewSchema = z.object({
116
+ /** at:// URI (or internal ref) identifying the post. */
117
+ uri: z.string(),
118
+ cid: z.string().optional(),
119
+ kind: z.enum(["prompt", "reply"]),
120
+ author: ProfileViewBasicSchema,
121
+ record: PostRecordPublicSchema,
122
+ /** Hydrated audio embed (signed URL + lifted transcript). */
123
+ embed: AudioEmbedViewSchema.optional(),
124
+ viewer: ViewerStateSchema
125
+ });
126
+
127
+ export { ActorProfileRecordSchema, AudioEmbedSchema, AudioEmbedViewSchema, AudioPostRecordSchema, AudioPostViewSchema, PostRecordPublicSchema, ReplyRefSchema, StrongRefSchema, TimedTranscriptSchema, TranscriptEnrichmentRecordSchema, TranscriptSegmentSchema, ViewerStateSchema };
@@ -0,0 +1,380 @@
1
+ import { FirestoreTimestampSchema, PromptRecordSchema, ReplyRecordSchema, OrganizationRecordSchema, OrganizationMemberRecordSchema, OrgInviteRecordSchema } from './chunk-3WZJXJNU.js';
2
+ import { __objRest, __spreadProps, __spreadValues } from './chunk-ORMEWXMH.js';
3
+ import { z } from 'zod';
4
+
5
+ var ProfileViewBasicSchema = z.object({
6
+ id: z.string(),
7
+ handle: z.string().nullable().optional(),
8
+ // `displayName` and `bio` are `.nullable()` — Firestore stores `null` for
9
+ // empty values on these fields (see users-dependencies.ts), and Zod's
10
+ // `.optional()` alone rejects `null`. This hit dashboard rendering on the
11
+ // Phase 3.1 HTTP cutover, when transport-layer Zod validation started
12
+ // actually exercising the wire shape. Consumers already use truthy
13
+ // checks / `??` / `||`, so widening the type to include `null` is safe.
14
+ displayName: z.string().nullable().optional(),
15
+ avatarUrl: z.string().nullable().optional(),
16
+ bio: z.string().nullable().optional(),
17
+ /** Personal website link surfaced on the public profile. */
18
+ website: z.string().nullable().optional(),
19
+ /** Public links (label + URL) shown under the bio. */
20
+ links: z.array(z.object({
21
+ label: z.string(),
22
+ url: z.string()
23
+ })).optional(),
24
+ /**
25
+ * AT Protocol identity, surfaced on the public profile only when the user
26
+ * opts in (`UserRecord.showBlueskyPublicly === true`). Projection happens
27
+ * in the user dependency layer; this schema simply allows the field.
28
+ */
29
+ bluesky: z.object({
30
+ handle: z.string(),
31
+ did: z.string()
32
+ }).optional(),
33
+ stats: z.object({
34
+ followers: z.number().default(0),
35
+ following: z.number().default(0),
36
+ prompts: z.number().default(0)
37
+ }).optional(),
38
+ badges: z.array(z.string()).optional(),
39
+ isVerified: z.boolean().optional(),
40
+ createdAt: FirestoreTimestampSchema.optional()
41
+ });
42
+ function toProfileViewBasic(profile) {
43
+ const { id, handle, displayName, avatarUrl, bio, website, links, bluesky, showBlueskyPublicly, stats, badges, isVerified, createdAt } = profile;
44
+ return {
45
+ id,
46
+ handle,
47
+ displayName,
48
+ avatarUrl,
49
+ bio,
50
+ website,
51
+ links,
52
+ bluesky: showBlueskyPublicly ? bluesky : void 0,
53
+ stats,
54
+ badges,
55
+ isVerified,
56
+ createdAt
57
+ };
58
+ }
59
+ var ProfileViewDetailedSchema = ProfileViewBasicSchema.extend({
60
+ /** AT Protocol Identity link */
61
+ bluesky: z.object({
62
+ handle: z.string(),
63
+ did: z.string()
64
+ }).optional(),
65
+ usageIntent: z.string().nullable().optional(),
66
+ /** Hydrated RSS Data (fetched from sub-collection) */
67
+ rssSummary: z.object({
68
+ title: z.string().optional(),
69
+ description: z.string().optional(),
70
+ items: z.array(z.object({
71
+ title: z.string().optional(),
72
+ link: z.string().optional(),
73
+ content: z.string().optional(),
74
+ pubDate: z.string().optional()
75
+ })).optional(),
76
+ lastFetchedAt: FirestoreTimestampSchema.optional()
77
+ }).optional(),
78
+ promptAudioUrl: z.string().optional(),
79
+ /** @deprecated Use stats.prompts from ProfileViewBasic instead */
80
+ totalPrompts: z.number().optional(),
81
+ totalReplies: z.number().optional(),
82
+ favoritePromptId: z.string().optional()
83
+ });
84
+ var ProfileViewSelfSchema = ProfileViewDetailedSchema.extend({
85
+ phoneNumber: z.string().nullable().optional(),
86
+ email: z.string().optional(),
87
+ lastSeenAt: FirestoreTimestampSchema.optional(),
88
+ lastActiveAt: FirestoreTimestampSchema.optional(),
89
+ unreadReplyCount: z.number().default(0),
90
+ newReplierCount: z.number().default(0),
91
+ /**
92
+ * Account tier from UserRecord — surfaced on the self profile so the
93
+ * client can gate paid features (e.g. the Performance dashboard tab).
94
+ * Optional for legacy docs without the field; consumers should treat
95
+ * missing as `'free'`.
96
+ */
97
+ tier: z.enum(["free", "creator_pro"]).optional(),
98
+ /**
99
+ * Surfaces the linked Bluesky identity (handle + DID) on the public profile
100
+ * when true. Persisted on UserRecord; exposed in self/detailed views so the
101
+ * settings form can render the toggle's current state.
102
+ */
103
+ showBlueskyPublicly: z.boolean().optional(),
104
+ settings: z.object({
105
+ notifications: z.boolean().optional(),
106
+ theme: z.string().optional()
107
+ }).optional()
108
+ });
109
+ var ProfileViewAdminSchema = ProfileViewSelfSchema.extend({
110
+ blockedUsers: z.array(z.string()).optional(),
111
+ followers: z.array(z.string()).optional(),
112
+ following: z.array(z.string()).optional(),
113
+ reportCount: z.number().optional(),
114
+ isBanned: z.boolean().optional()
115
+ });
116
+ var ProfileViewSchema = ProfileViewAdminSchema;
117
+ var PromptViewSchema = z.object({
118
+ /** AT Protocol URI (e.g. at://did:plc.../app.../123) */
119
+ uri: z.string().optional(),
120
+ /** IPFS Content ID */
121
+ cid: z.string().optional(),
122
+ /** The raw prompt data */
123
+ record: PromptRecordSchema,
124
+ /** The hydrated author profile */
125
+ author: ProfileViewSchema,
126
+ /** Total number of replies */
127
+ replyCount: z.number().default(0),
128
+ likeCount: z.number().default(0),
129
+ updatedAt: FirestoreTimestampSchema.optional(),
130
+ lastReplyAt: FirestoreTimestampSchema.optional(),
131
+ tags: z.array(z.string()).optional(),
132
+ visibility: z.enum(["public", "private", "unlisted", "archived"]).default("public"),
133
+ analytics: z.object({
134
+ views: z.number().default(0),
135
+ listens: z.number().default(0),
136
+ /**
137
+ * Mean engagementScore (1–10) across this prompt's `status: 'live'`
138
+ * replies that completed AI enrichment. `null` when no such replies
139
+ * exist (0 would alias a valid bottom-of-range score). Derived in
140
+ * hydration from the prompt doc's `engagementScoreSum / Count`.
141
+ */
142
+ avgEngagementScore: z.number().nullable().optional(),
143
+ /**
144
+ * Counts of `status: 'live'` AI-enriched replies grouped by sentiment.
145
+ * Keys lowercase by convention; source enum
146
+ * (`Positive | Neutral | Negative`) is mapped at the write site.
147
+ */
148
+ sentimentBreakdown: z.object({
149
+ positive: z.number(),
150
+ neutral: z.number(),
151
+ negative: z.number()
152
+ }).optional()
153
+ }).optional(),
154
+ moderation: z.object({
155
+ flagged: z.boolean().default(false),
156
+ reason: z.string().optional()
157
+ }).optional(),
158
+ // AI Enrichment Fields (Hydrated from Record but hidden from Record Schema)
159
+ aiLabels: z.array(z.string()).optional(),
160
+ aiSummary: z.string().optional(),
161
+ aiStatus: z.enum(["pending", "complete", "error"]).optional(),
162
+ aiError: z.string().optional(),
163
+ transcription: z.string().optional()
164
+ });
165
+ var PromptViewPublicSchema = PromptViewSchema.omit({
166
+ analytics: true,
167
+ moderation: true,
168
+ aiLabels: true,
169
+ aiSummary: true,
170
+ aiStatus: true,
171
+ aiError: true,
172
+ transcription: true
173
+ }).extend({
174
+ author: ProfileViewBasicSchema
175
+ });
176
+ function toPromptViewPublic(prompt) {
177
+ const _a = prompt, { analytics, moderation, aiLabels, aiSummary, aiStatus, aiError, transcription } = _a, rest = __objRest(_a, ["analytics", "moderation", "aiLabels", "aiSummary", "aiStatus", "aiError", "transcription"]);
178
+ return __spreadProps(__spreadValues({}, rest), {
179
+ author: toProfileViewBasic(rest.author)
180
+ });
181
+ }
182
+ var ReplyViewSchema = z.object({
183
+ record: ReplyRecordSchema,
184
+ author: ProfileViewSchema,
185
+ recipient: ProfileViewSchema,
186
+ /** GCS Storage Path for audio file */
187
+ storagePath: z.string().optional(),
188
+ duration: z.number().optional(),
189
+ updatedAt: FirestoreTimestampSchema.optional(),
190
+ isRead: z.boolean(),
191
+ readBy: z.array(z.string()).default([]),
192
+ isDeleted: z.boolean().default(false),
193
+ reactions: z.record(z.string(), z.number()).optional(),
194
+ moderation: z.object({
195
+ flagged: z.boolean().default(false),
196
+ reason: z.string().optional()
197
+ }).optional(),
198
+ // AI Enrichment Fields (Hydrated from Record but hidden from Record Schema)
199
+ aiLabels: z.array(z.string()).optional(),
200
+ aiSummary: z.string().optional(),
201
+ aiStatus: z.enum(["pending", "complete", "error", "skipped_too_short"]).optional(),
202
+ aiError: z.string().optional(),
203
+ transcription: z.string().optional(),
204
+ sentiment: z.enum(["Positive", "Negative", "Neutral"]).optional(),
205
+ /** Must match the widened enum in `ReplyRecordSchema.energyLevel`. */
206
+ energyLevel: z.enum(["High", "Low", "Neutral"]).optional(),
207
+ engagementScore: z.number().min(1).max(10).optional(),
208
+ // === Voice-isolation enrichment (lifted; see specs/ai-enrichment-split.md) ===
209
+ //
210
+ // `enhancedAudioUrl` is the only one of these the public view keeps —
211
+ // it replaces `audioUrl` for downstream players when present.
212
+ // `enhancedStoragePath` is private (storage paths are server-side
213
+ // bookkeeping).
214
+ enhancedAudioUrl: z.string().url().optional(),
215
+ enhancedStoragePath: z.string().optional(),
216
+ // === Social-share video enrichment (lifted; creator-only) ===
217
+ //
218
+ // All `socialVideo*` fields are stripped by `toReplyViewPublic` —
219
+ // the URL points at a generated artifact the creator may share to
220
+ // social media, NOT at content the reply page should render. Status /
221
+ // error / source-audio are job-tracking state.
222
+ socialVideoUrl: z.string().url().optional(),
223
+ socialVideoStoragePath: z.string().optional(),
224
+ socialVideoStatus: z.enum(["pending", "complete", "error"]).optional(),
225
+ socialVideoError: z.string().optional(),
226
+ socialVideoSourceAudio: z.string().optional(),
227
+ /** @private Confirmed listener phone number (never exposed publicly) */
228
+ listenerPhoneNumber: z.string().regex(/^\+[1-9]\d{1,14}$/).optional(),
229
+ /**
230
+ * @private Per-viewer private notes about this reply (CRM enrichment).
231
+ * Lifted into the view from `enrichments/replies/{id}.notes` by the
232
+ * hydrator when the viewer is the prompt author. Never populated for
233
+ * non-author viewers; defensively stripped by `toReplyViewPublic`.
234
+ */
235
+ notes: z.string().optional()
236
+ });
237
+ var ReplyViewPublicSchema = ReplyViewSchema.omit({
238
+ listenerPhoneNumber: true,
239
+ notes: true,
240
+ // AI cluster — only `transcription` survives.
241
+ aiStatus: true,
242
+ aiError: true,
243
+ aiSummary: true,
244
+ aiLabels: true,
245
+ sentiment: true,
246
+ energyLevel: true,
247
+ engagementScore: true,
248
+ // Voice isolation — keep enhancedAudioUrl, strip the path.
249
+ enhancedStoragePath: true,
250
+ // Social-video — entire cluster is creator-only.
251
+ socialVideoUrl: true,
252
+ socialVideoStoragePath: true,
253
+ socialVideoStatus: true,
254
+ socialVideoError: true,
255
+ socialVideoSourceAudio: true
256
+ });
257
+ function toReplyViewPublic(reply) {
258
+ const _a = reply, {
259
+ listenerPhoneNumber,
260
+ notes,
261
+ aiStatus: aiStatus,
262
+ aiError,
263
+ aiSummary,
264
+ aiLabels,
265
+ sentiment,
266
+ energyLevel,
267
+ engagementScore,
268
+ enhancedStoragePath: enhancedStoragePath,
269
+ socialVideoUrl: socialVideoUrl,
270
+ socialVideoStoragePath,
271
+ socialVideoStatus,
272
+ socialVideoError,
273
+ socialVideoSourceAudio
274
+ } = _a, publicReply = __objRest(_a, [
275
+ "listenerPhoneNumber",
276
+ "notes",
277
+ // AI cluster (only transcription stays public)
278
+ "aiStatus",
279
+ "aiError",
280
+ "aiSummary",
281
+ "aiLabels",
282
+ "sentiment",
283
+ "energyLevel",
284
+ "engagementScore",
285
+ // Voice isolation (only enhancedAudioUrl stays public)
286
+ "enhancedStoragePath",
287
+ // Social-video (entire cluster is creator-only)
288
+ "socialVideoUrl",
289
+ "socialVideoStoragePath",
290
+ "socialVideoStatus",
291
+ "socialVideoError",
292
+ "socialVideoSourceAudio"
293
+ ]);
294
+ return publicReply;
295
+ }
296
+ var OrganizationViewSchema = z.object({
297
+ record: OrganizationRecordSchema,
298
+ memberCount: z.number().default(1),
299
+ currentUserRole: z.enum(["owner", "admin", "member"]).optional()
300
+ });
301
+ var OrganizationMemberViewSchema = z.object({
302
+ record: OrganizationMemberRecordSchema,
303
+ profile: ProfileViewBasicSchema
304
+ });
305
+ var OrgInviteViewSchema = z.object({
306
+ record: OrgInviteRecordSchema,
307
+ /** Display name of the user who sent the invite */
308
+ inviterName: z.string().optional(),
309
+ /** Name of the organization */
310
+ orgName: z.string()
311
+ });
312
+ var PromptRepliersSchema = z.object({
313
+ promptId: z.string(),
314
+ repliers: z.record(z.string(), z.object({
315
+ firstReplyAt: FirestoreTimestampSchema,
316
+ lastReplyAt: FirestoreTimestampSchema,
317
+ replyCount: z.number()
318
+ }))
319
+ });
320
+ var PromptWithRepliesSchema = PromptViewSchema.extend({
321
+ replies: z.array(ReplyViewSchema)
322
+ });
323
+ var ReplierSchema = z.object({
324
+ handle: z.string(),
325
+ /** ISO date string */
326
+ lastReplyDate: z.string(),
327
+ /** ISO date string */
328
+ firstReplyAt: z.string(),
329
+ totalReplies: z.number()
330
+ });
331
+ var HandleResolutionSchema = z.discriminatedUnion("type", [
332
+ // Public endpoint (`GET /resolve/:handle`) — project to the basic shape so
333
+ // PII/admin fields (email, phoneNumber, settings, blockedUsers, …) never
334
+ // cross the public API boundary. See `toProfileViewBasic`.
335
+ z.object({ type: z.literal("user"), profile: ProfileViewBasicSchema }),
336
+ z.object({ type: z.literal("org"), org: OrganizationViewSchema })
337
+ ]);
338
+ var UserProfileDataSchema = z.object({
339
+ // Public endpoint (`GET /users/:handle/profile`) — basic shape only, so the
340
+ // owner's PII/admin fields never serialize to anonymous callers.
341
+ profileUser: ProfileViewBasicSchema,
342
+ // Each prompt's nested `author` is the profile owner, hydrated from the
343
+ // wide admin profile (`getPromptsForUser` prefetches via getUserDataByUid).
344
+ // Use the PUBLIC prompt/reply shapes so that nested author PII (email,
345
+ // phoneNumber, settings, …) and owner-only prompt enrichment never leak.
346
+ allPromptsWithReplies: z.array(PromptViewPublicSchema.extend({
347
+ replies: z.array(ReplyViewPublicSchema)
348
+ })),
349
+ repliers: z.array(ReplierSchema)
350
+ });
351
+ var RssSummarySchema = z.object({
352
+ title: z.string().optional(),
353
+ description: z.string().optional(),
354
+ image: z.string().optional(),
355
+ link: z.string().optional(),
356
+ items: z.array(z.object({
357
+ title: z.string().optional(),
358
+ link: z.string().optional(),
359
+ content: z.string().optional(),
360
+ pubDate: z.string().optional()
361
+ })).optional(),
362
+ lastFetchedAt: FirestoreTimestampSchema.optional()
363
+ });
364
+ var OrgProfileDataSchema = z.object({
365
+ org: OrganizationViewSchema,
366
+ prompts: z.array(PromptViewSchema),
367
+ rssSummary: RssSummarySchema.nullable()
368
+ });
369
+ var EnrichedReplierSchema = z.object({
370
+ profile: ProfileViewBasicSchema,
371
+ totalReplies: z.number(),
372
+ /** ISO date string */
373
+ lastReplyDate: z.string(),
374
+ /** ISO date string */
375
+ firstReplyAt: z.string(),
376
+ /** Phone number for anonymous repliers (from Firebase Auth, only visible to prompt author) */
377
+ phoneNumber: z.string().optional()
378
+ });
379
+
380
+ export { EnrichedReplierSchema, HandleResolutionSchema, OrgInviteViewSchema, OrgProfileDataSchema, OrganizationMemberViewSchema, OrganizationViewSchema, ProfileViewAdminSchema, ProfileViewBasicSchema, ProfileViewDetailedSchema, ProfileViewSchema, ProfileViewSelfSchema, PromptRepliersSchema, PromptViewPublicSchema, PromptViewSchema, PromptWithRepliesSchema, ReplierSchema, ReplyViewPublicSchema, ReplyViewSchema, RssSummarySchema, UserProfileDataSchema, toProfileViewBasic, toPromptViewPublic, toReplyViewPublic };
@@ -0,0 +1,40 @@
1
+ import { __spreadValues } from './chunk-ORMEWXMH.js';
2
+
3
+ // observability/report-error.ts
4
+ var REPORTED_ERROR_EVENT_TYPE = "type.googleapis.com/google.devtools.clouderrorreporting.v1beta1.ReportedErrorEvent";
5
+ function serviceName() {
6
+ var _a, _b;
7
+ return (_b = (_a = process.env.K_SERVICE) != null ? _a : process.env.SERVICE_NAME) != null ? _b : "unknown";
8
+ }
9
+ function serviceVersion() {
10
+ var _a, _b;
11
+ return (_b = (_a = process.env.COMMIT_SHA) != null ? _a : process.env.K_REVISION) != null ? _b : "dev";
12
+ }
13
+ function formatUnknownError(err) {
14
+ var _a;
15
+ if (typeof err === "string") return err;
16
+ try {
17
+ return (_a = JSON.stringify(err)) != null ? _a : String(err);
18
+ } catch (e) {
19
+ return String(err);
20
+ }
21
+ }
22
+ function buildReportedErrorEvent(err, context) {
23
+ var _a;
24
+ const error = err instanceof Error ? err : new Error(formatUnknownError(err));
25
+ const message = (_a = error.stack) != null ? _a : `${error.name}: ${error.message}`;
26
+ return __spreadValues({
27
+ severity: "ERROR",
28
+ "@type": REPORTED_ERROR_EVENT_TYPE,
29
+ message,
30
+ serviceContext: { service: serviceName(), version: serviceVersion() }
31
+ }, context && Object.keys(context).length > 0 ? { context } : {});
32
+ }
33
+ function reportError(err, context) {
34
+ try {
35
+ console.error(JSON.stringify(buildReportedErrorEvent(err, context)));
36
+ } catch (e) {
37
+ }
38
+ }
39
+
40
+ export { buildReportedErrorEvent, reportError };
@@ -0,0 +1,33 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __defProps = Object.defineProperties;
3
+ var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
4
+ var __getOwnPropSymbols = Object.getOwnPropertySymbols;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __propIsEnum = Object.prototype.propertyIsEnumerable;
7
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
8
+ var __spreadValues = (a, b) => {
9
+ for (var prop in b || (b = {}))
10
+ if (__hasOwnProp.call(b, prop))
11
+ __defNormalProp(a, prop, b[prop]);
12
+ if (__getOwnPropSymbols)
13
+ for (var prop of __getOwnPropSymbols(b)) {
14
+ if (__propIsEnum.call(b, prop))
15
+ __defNormalProp(a, prop, b[prop]);
16
+ }
17
+ return a;
18
+ };
19
+ var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
20
+ var __objRest = (source, exclude) => {
21
+ var target = {};
22
+ for (var prop in source)
23
+ if (__hasOwnProp.call(source, prop) && exclude.indexOf(prop) < 0)
24
+ target[prop] = source[prop];
25
+ if (source != null && __getOwnPropSymbols)
26
+ for (var prop of __getOwnPropSymbols(source)) {
27
+ if (exclude.indexOf(prop) < 0 && __propIsEnum.call(source, prop))
28
+ target[prop] = source[prop];
29
+ }
30
+ return target;
31
+ };
32
+
33
+ export { __objRest, __spreadProps, __spreadValues };
@@ -0,0 +1,24 @@
1
+ import { ProfileViewBasicSchema, ProfileViewSelfSchema } from './chunk-EA4OONDV.js';
2
+ import { FirestoreTimestampSchema } from './chunk-3WZJXJNU.js';
3
+ import { z } from 'zod';
4
+
5
+ var PublicProfileDtoSchema = ProfileViewBasicSchema.extend({
6
+ bluesky: z.object({
7
+ handle: z.string(),
8
+ did: z.string()
9
+ }).optional(),
10
+ rssFeedUrl: z.string().nullable().optional()
11
+ });
12
+ var PublicReplyDtoSchema = z.object({
13
+ id: z.string(),
14
+ audioUrl: z.string().url(),
15
+ duration: z.number().optional(),
16
+ createdAt: FirestoreTimestampSchema,
17
+ transcription: z.string().optional(),
18
+ sentiment: z.enum(["Positive", "Negative", "Neutral"]).optional(),
19
+ aiSummary: z.string().optional(),
20
+ author: PublicProfileDtoSchema
21
+ });
22
+ var ActorViewSchema = ProfileViewSelfSchema;
23
+
24
+ export { ActorViewSchema, PublicProfileDtoSchema, PublicReplyDtoSchema };
@@ -0,0 +1,32 @@
1
+ // nsid.ts
2
+ var NSID = {
3
+ // Legacy Vox Pop record types (deprecated; removed in Stream 4).
4
+ Prompt: "com.voxpop.audio.prompt",
5
+ Reply: "com.voxpop.audio.reply",
6
+ Profile: "com.voxpop.actor.profile",
7
+ // Antiphony canonical record types (dev.antiphony.*). Added additively in
8
+ // Stream 1; see lexicons/dev/antiphony/ + packages/shared/types/audio.ts.
9
+ AudioPost: "dev.antiphony.audio.post",
10
+ AudioTranscript: "dev.antiphony.audio.transcript",
11
+ ActorProfile: "dev.antiphony.actor.profile"
12
+ };
13
+ var EMBED_NSID = {
14
+ Audio: "dev.antiphony.embed.audio",
15
+ RecordWithAudio: "dev.antiphony.embed.recordWithAudio"
16
+ };
17
+ var COLLECTIONS = {
18
+ [NSID.Prompt]: "prompts",
19
+ [NSID.Reply]: "replies",
20
+ [NSID.Profile]: "users",
21
+ // Antiphony: one post collection + the transcript enrichment namespace.
22
+ [NSID.AudioPost]: "posts",
23
+ [NSID.AudioTranscript]: "audio_transcripts",
24
+ [NSID.ActorProfile]: "users"
25
+ };
26
+ function nsidForCollection(collection) {
27
+ const entries = Object.entries(COLLECTIONS);
28
+ const match = entries.find(([, col]) => col === collection);
29
+ return match == null ? void 0 : match[0];
30
+ }
31
+
32
+ export { COLLECTIONS, EMBED_NSID, NSID, nsidForCollection };
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Typed service errors for consistent error handling across the API layer.
3
+ *
4
+ * Usage:
5
+ * throw new NotFoundError('Prompt not found');
6
+ * throw new ConflictError('Handle is already taken');
7
+ *
8
+ * The `withErrorHandler` middleware catches these and maps `error.status`
9
+ * directly to the HTTP response status code.
10
+ */
11
+ declare class ServiceError extends Error {
12
+ /** HTTP status code to return when this error reaches the API layer. */
13
+ readonly status: number;
14
+ /** Optional structured details (e.g., Zod issues). */
15
+ readonly details?: unknown;
16
+ /** Stable, machine-readable error identifier surfaced in the response envelope's `error.code`. */
17
+ readonly code?: string;
18
+ constructor(message: string, status: number, details?: unknown, code?: string);
19
+ }
20
+ /** 400 — Invalid input or Zod validation failure. */
21
+ declare class ValidationError extends ServiceError {
22
+ constructor(message: string, details?: unknown);
23
+ }
24
+ /** 401 — Missing or invalid authentication credentials. */
25
+ declare class UnauthorizedError extends ServiceError {
26
+ constructor(message?: string);
27
+ }
28
+ /** 403 — Authenticated but insufficient permissions. */
29
+ declare class ForbiddenError extends ServiceError {
30
+ constructor(message?: string);
31
+ }
32
+ /** 404 — Resource does not exist. */
33
+ declare class NotFoundError extends ServiceError {
34
+ constructor(message?: string);
35
+ }
36
+ /** 409 — Conflict with existing state (e.g., handle already taken). */
37
+ declare class ConflictError extends ServiceError {
38
+ constructor(message?: string);
39
+ }
40
+ /** 429 — Client has sent too many requests. */
41
+ declare class RateLimitError extends ServiceError {
42
+ constructor(message?: string);
43
+ }
44
+
45
+ export { ConflictError, ForbiddenError, NotFoundError, RateLimitError, ServiceError, UnauthorizedError, ValidationError };
@@ -0,0 +1,2 @@
1
+ export { ConflictError, ForbiddenError, NotFoundError, RateLimitError, ServiceError, UnauthorizedError, ValidationError } from '../chunk-7LW2FHLD.js';
2
+ import '../chunk-ORMEWXMH.js';