@antiphony/shared 0.3.0 → 0.5.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 (45) hide show
  1. package/README.md +4 -4
  2. package/dist/cjs/api-codecs.cjs +149 -142
  3. package/dist/cjs/api-codecs.d.cts +157 -68
  4. package/dist/cjs/index.cjs +187 -192
  5. package/dist/cjs/index.d.cts +310 -7
  6. package/dist/cjs/nsid.cjs +6 -9
  7. package/dist/cjs/nsid.d.cts +7 -8
  8. package/dist/cjs/types/audio.cjs +133 -119
  9. package/dist/cjs/types/audio.d.cts +343 -199
  10. package/dist/cjs/types/blob.cjs +5 -9
  11. package/dist/cjs/types/blob.d.cts +21 -21
  12. package/dist/cjs/types/records.cjs +0 -45
  13. package/dist/cjs/types/records.d.cts +1 -103
  14. package/dist/esm/api-codecs.d.ts +187 -98
  15. package/dist/esm/api-codecs.js +4 -5
  16. package/dist/esm/chunk-D655OH2I.js +35 -0
  17. package/dist/esm/chunk-F5ZKFJL2.js +39 -0
  18. package/dist/esm/{chunk-6LROFBKK.js → chunk-SBYNIQRZ.js} +7 -9
  19. package/dist/esm/chunk-SMK4OZNU.js +17 -0
  20. package/dist/esm/chunk-TVOXIQKF.js +294 -0
  21. package/dist/esm/index.d.ts +310 -7
  22. package/dist/esm/index.js +5 -7
  23. package/dist/esm/nsid.d.ts +7 -8
  24. package/dist/esm/nsid.js +1 -1
  25. package/dist/esm/types/audio.d.ts +420 -276
  26. package/dist/esm/types/audio.js +3 -4
  27. package/dist/esm/types/blob.d.ts +21 -21
  28. package/dist/esm/types/blob.js +1 -1
  29. package/dist/esm/types/records.d.ts +1 -103
  30. package/dist/esm/types/records.js +1 -1
  31. package/package.json +94 -26
  32. package/dist/cjs/types/api.cjs +0 -166
  33. package/dist/cjs/types/api.d.cts +0 -242
  34. package/dist/cjs/types/views.cjs +0 -179
  35. package/dist/cjs/types/views.d.cts +0 -726
  36. package/dist/esm/chunk-5EHV73BA.js +0 -20
  37. package/dist/esm/chunk-F7IHVM34.js +0 -144
  38. package/dist/esm/chunk-FDM5FDN4.js +0 -46
  39. package/dist/esm/chunk-IHIBHQBJ.js +0 -78
  40. package/dist/esm/chunk-R6SBR3IG.js +0 -97
  41. package/dist/esm/chunk-Y6HNNRVD.js +0 -12
  42. package/dist/esm/types/api.d.ts +0 -242
  43. package/dist/esm/types/api.js +0 -4
  44. package/dist/esm/types/views.d.ts +0 -726
  45. package/dist/esm/types/views.js +0 -3
@@ -0,0 +1,294 @@
1
+ import { FirestoreTimestampSchema } from './chunk-D655OH2I.js';
2
+ import { BlobRefSchema } from './chunk-SMK4OZNU.js';
3
+ import { z } from 'zod';
4
+
5
+ var ProcessingStageStatusSchema = z.enum(["pending", "ready", "failed", "skipped"]);
6
+ var PROCESSING_STAGES = ["denoise", "trim", "transcribe", "waveform"];
7
+ var ProcessingStageSchema = z.enum(PROCESSING_STAGES);
8
+ var BYTE_MUTATING_STAGES = ["denoise", "trim"];
9
+ var DERIVED_STAGES = ["transcribe", "waveform"];
10
+ var ProcessingRequestSchema = z.object({
11
+ transcribe: z.boolean().optional(),
12
+ denoise: z.boolean().optional(),
13
+ trim: z.boolean().optional(),
14
+ waveform: z.boolean().optional(),
15
+ /**
16
+ * Whether a completed byte-mutating stage should invalidate and recompute
17
+ * the derived artifacts that describe the old audio. Defaults to **true**
18
+ * — a transcript of superseded audio is wrong, not merely stale.
19
+ *
20
+ * `false` opts out, for an app that would rather keep the existing
21
+ * transcript than pay to regenerate it. It does NOT name a stage, so a
22
+ * request carrying only `reprocess` requests no work.
23
+ */
24
+ reprocess: z.boolean().optional()
25
+ });
26
+ var ProcessingStageMapSchema = z.object({
27
+ transcribe: ProcessingStageStatusSchema.optional(),
28
+ denoise: ProcessingStageStatusSchema.optional(),
29
+ trim: ProcessingStageStatusSchema.optional(),
30
+ waveform: ProcessingStageStatusSchema.optional()
31
+ });
32
+ var ResolvedProcessingSchema = ProcessingStageMapSchema.extend({
33
+ reprocess: z.boolean().optional()
34
+ });
35
+ var ProcessingStateSchema = ResolvedProcessingSchema.extend({
36
+ /**
37
+ * Content CID of the processed audio variant — the composed output of every
38
+ * byte-mutating stage that has completed. The record's own
39
+ * `embed.audio.ref.$link` stays the ORIGINAL CID (immutable content
40
+ * address); only the read-time view swaps playback to this variant.
41
+ */
42
+ processedBlobCid: z.string().optional(),
43
+ /**
44
+ * MIME type of the processed variant. Present because providers may
45
+ * TRANSCODE — the ElevenLabs Voice Isolator returns MP3 regardless of what
46
+ * it is given — so the variant's type cannot be assumed to match
47
+ * `embed.audio.mimeType`. Anything reading the variant's bytes must use
48
+ * this, not the embed's.
49
+ */
50
+ processedMimeType: z.string().optional(),
51
+ /**
52
+ * Duration of the processed variant, when a byte-mutating stage changed it
53
+ * (i.e. trim). Absent when the variant's duration matches the original.
54
+ */
55
+ processedDurationMs: z.number().int().min(0).optional(),
56
+ /**
57
+ * Peaks for the processed variant, once the `waveform` stage completes.
58
+ * Same normalization and bounds as `embed.waveform` (0–100, max 1000), so
59
+ * a view can never carry a larger payload than the record allows.
60
+ */
61
+ waveformPeaks: z.array(z.number().int().min(0).max(100)).max(1e3).optional(),
62
+ /**
63
+ * When the current runner's exclusive claim on this post expires.
64
+ *
65
+ * Queue delivery is at-least-once, so the same job can arrive twice and
66
+ * run CONCURRENTLY. `process()` is idempotent under sequential retry — it
67
+ * acts on `pending` and re-does nothing already settled — but two passes
68
+ * interleaved is a different failure: both read the same `pending` state,
69
+ * both bill the provider for the same stage, and both write
70
+ * `processedBlobCid`, so the surviving variant is whichever finished last
71
+ * and the other's blob is orphaned.
72
+ *
73
+ * A runner claims this field transactionally before doing any work and
74
+ * clears it when finished; a second runner finding it unexpired declines
75
+ * and returns. It is an EXPIRY, not a boolean lock, because the holder can
76
+ * die mid-run (instance recycled, process killed) with no chance to
77
+ * release — a plain flag would strand the post permanently, where a lapsed
78
+ * lease lets the next delivery pick it up.
79
+ *
80
+ * Internal, like the variant fields above: `toProcessingView` projects
81
+ * stages only, so this never reaches a client.
82
+ */
83
+ leaseUntil: FirestoreTimestampSchema.optional(),
84
+ updatedAt: FirestoreTimestampSchema
85
+ });
86
+ var ProcessingViewSchema = ProcessingStageMapSchema;
87
+ function toProcessingView(state) {
88
+ const view = {};
89
+ for (const stage of PROCESSING_STAGES) {
90
+ if (state[stage] !== void 0) view[stage] = state[stage];
91
+ }
92
+ return view;
93
+ }
94
+ function resolveAudioVariant(canonical, state) {
95
+ var _a;
96
+ if (!state) return canonical;
97
+ const hasVariant = state.processedBlobCid !== void 0;
98
+ const peaksAreCurrent = state.waveform === "ready" && state.waveformPeaks !== void 0;
99
+ return {
100
+ blobCid: hasVariant ? state.processedBlobCid : canonical.blobCid,
101
+ durationMs: hasVariant ? (_a = state.processedDurationMs) != null ? _a : canonical.durationMs : canonical.durationMs,
102
+ waveform: peaksAreCurrent ? state.waveformPeaks : canonical.waveform
103
+ };
104
+ }
105
+
106
+ // types/audio.ts
107
+ var StrongRefSchema = z.object({
108
+ uri: z.string().regex(/^at:\/\/.+/, "Must be an at:// URI"),
109
+ cid: z.string()
110
+ });
111
+ var ReplyRefSchema = z.object({
112
+ root: StrongRefSchema,
113
+ parent: StrongRefSchema
114
+ });
115
+ var AudioEmbedSchema = z.object({
116
+ $type: z.literal("dev.antiphony.embed.audio"),
117
+ /** The audio bytes as a content-addressed blob ref (`ref.$link` = CID). */
118
+ audio: BlobRefSchema,
119
+ /** Duration in MILLISECONDS (platform-wide unit; not seconds). */
120
+ durationMs: z.number().int().min(0).optional(),
121
+ /** User-authored short description (audio analogue of image alt). NOT the transcript. */
122
+ alt: z.string().max(1e4).optional(),
123
+ /** Pre-computed waveform peaks (normalized 0–100) for instant rendering. */
124
+ waveform: z.array(z.number().int().min(0).max(100)).max(1e3).optional()
125
+ });
126
+ var TranscriptSegmentSchema = z.object({
127
+ startMs: z.number().int().min(0),
128
+ endMs: z.number().int().min(0),
129
+ text: z.string()
130
+ }).refine((s) => s.endMs >= s.startMs, {
131
+ message: "endMs must be >= startMs",
132
+ path: ["endMs"]
133
+ });
134
+ var TimedTranscriptSchema = z.object({
135
+ segments: z.array(TranscriptSegmentSchema),
136
+ text: z.string().optional()
137
+ });
138
+ var AudioEmbedViewSchema = z.object({
139
+ $type: z.literal("dev.antiphony.embed.audio#view"),
140
+ /**
141
+ * `url`, `durationMs` and `waveform` are RESOLVED, not copied: once
142
+ * processing has produced an audio variant they describe that variant
143
+ * rather than the bytes the client uploaded. They always agree with one
144
+ * another — a duration and a set of peaks are only meaningful against the
145
+ * audio `url` actually points at.
146
+ *
147
+ * A client that stored `durationMs` at upload time should therefore expect
148
+ * it to change (trim removes leading/trailing silence), and should render
149
+ * these three as a set rather than caching them independently. The record's
150
+ * originals are immutable and unaffected; this is a read-time resolution.
151
+ */
152
+ url: z.string().url(),
153
+ durationMs: z.number().int().min(0).optional(),
154
+ // `alt` is copied from the stored embed; keep the same bounds so a view can
155
+ // never carry a larger payload than the record allows.
156
+ alt: z.string().max(1e4).optional(),
157
+ waveform: z.array(z.number().int().min(0).max(100)).max(1e3).optional(),
158
+ /** Lifted from the transcript enrichment record; absent until transcription completes. */
159
+ transcript: TimedTranscriptSchema.optional(),
160
+ /**
161
+ * Per-stage audio-processing status (denoise / trim / transcribe /
162
+ * waveform), when the app opted into processing. Absent otherwise. A
163
+ * `pending` stage means the client should poll (or re-render) for the
164
+ * result — including a stage that returns to `pending` after having been
165
+ * `ready`, which is how a recompute surfaces. Once a byte-mutating stage
166
+ * completes, `url`/`durationMs`/`waveform` above already resolve to the
167
+ * processed audio variant. See `types/processing.ts`.
168
+ */
169
+ processing: ProcessingViewSchema.optional()
170
+ });
171
+ var AudioPostRecordSchema = z.object({
172
+ /** Storage id (rkey/doc id). */
173
+ id: z.string(),
174
+ /**
175
+ * Content CID of the canonical lexicon record (CIDv1, dag-cbor, sha2-256
176
+ * — the AT Protocol record-CID rule). Computed at write time over the
177
+ * lexicon projection (public fields only, NOT the storage/tenancy fields
178
+ * below), so StrongRefs built from it are verifiable content addresses.
179
+ */
180
+ cid: z.string(),
181
+ // --- Tenancy + facets (storage-indexed; NOT in the lexicon) ---
182
+ /** Origin app that created this record — the multi-tenant isolation key. */
183
+ originAppId: z.string(),
184
+ /** Authoring user. A queryable facet, not the tenancy boundary. */
185
+ authorId: z.string(),
186
+ /** Optional AT Protocol identity of the author (facet). */
187
+ authorDid: z.string().optional(),
188
+ /** Optional org context (facet). */
189
+ orgId: z.string().nullable().optional(),
190
+ /** Denormalized from `reply` presence: `reply` set ⇒ 'reply', else 'prompt'. */
191
+ kind: z.enum(["prompt", "reply"]),
192
+ /**
193
+ * Branch participant pair (author ids) for reply gating — the parties to a
194
+ * reply's sub-thread: the creator (thread-root author) + the responder who
195
+ * opened the branch. Set on replies (deduped, 1–2 ids); absent on prompts
196
+ * (a prompt's repliers are the app's audience policy, not a fixed pair).
197
+ * Inherited down the branch so reply gating is an O(1) field check, never a
198
+ * thread walk.
199
+ */
200
+ threadParticipants: z.array(z.string()).optional(),
201
+ /**
202
+ * Author of this reply's thread ROOT (the prompt) — the reply's recipient,
203
+ * the person whose "replies to me" feed it lands in. Denormalized at write
204
+ * time so "replies whose root author is X" is a cheap composite-index query
205
+ * (see `queryByRootAuthor`). Set on replies (`kind === 'reply'`); absent on
206
+ * prompts. Storage-layer facet, NOT in the public lexicon or the record CID.
207
+ */
208
+ rootAuthorId: z.string().optional(),
209
+ /**
210
+ * Async audio-processing state (transcribe / denoise), present iff the app
211
+ * opted into processing on create. Mutated by the processing worker after
212
+ * the post is created — storage-layer, NOT in the lexicon or the record
213
+ * CID. See `types/processing.ts`.
214
+ */
215
+ processing: ProcessingStateSchema.optional(),
216
+ // --- Lexicon fields (public contract) ---
217
+ /** User-authored text (bsky-semantic). May be empty for pure-audio posts. NEVER the transcript. */
218
+ text: z.string().max(3e3),
219
+ /** Optional headline; a prompt feature, not the discriminator. */
220
+ title: z.string().max(3e3).optional(),
221
+ /** Audio (or other) attachment. Audio posts carry an `AudioEmbed`. */
222
+ embed: AudioEmbedSchema.optional(),
223
+ /** Present iff this post is a reply (StrongRef root + parent). */
224
+ reply: ReplyRefSchema.optional(),
225
+ /** BCP-47 language tags for the text. */
226
+ langs: z.array(z.string()).max(3).optional(),
227
+ /** Author-applied self-label values (content warnings). Simplified from the
228
+ * lexicon's `com.atproto.label.defs#selfLabels` to the bare value strings. */
229
+ selfLabels: z.array(z.string()).optional(),
230
+ createdAt: FirestoreTimestampSchema
231
+ }).refine(
232
+ // `kind` is denormalized from `reply` presence at write time; enforce the
233
+ // invariant so an inconsistent record can't be written or read silently.
234
+ // A reply has `reply`, no `title`, and a stamped `rootAuthorId` (its
235
+ // recipient facet); a prompt has none of those.
236
+ (r) => r.kind === "reply" ? !!r.reply && r.title === void 0 && r.rootAuthorId !== void 0 : !r.reply && r.rootAuthorId === void 0,
237
+ {
238
+ message: "kind must match reply presence: 'reply' \u21D2 reply set, no title, rootAuthorId set; 'prompt' \u21D2 no reply, no rootAuthorId",
239
+ path: ["kind"]
240
+ }
241
+ );
242
+ var TranscriptEnrichmentRecordSchema = z.object({
243
+ id: z.string(),
244
+ /** The post whose audio this transcribes. */
245
+ subject: StrongRefSchema,
246
+ transcript: TimedTranscriptSchema,
247
+ /** BCP-47 language tag of the transcript. */
248
+ lang: z.string().optional(),
249
+ /** Model/provider provenance (the generator is a pluggable port). */
250
+ model: z.string().optional(),
251
+ createdAt: FirestoreTimestampSchema
252
+ });
253
+ var ActorProfileRecordSchema = z.object({
254
+ handle: z.string().min(3).max(20).optional(),
255
+ usageIntent: z.string().max(100).optional(),
256
+ rssFeed: z.string().url().optional()
257
+ });
258
+ var ViewerStateSchema = z.object({
259
+ /** True when the authenticated caller authored this post. */
260
+ isAuthor: z.boolean().default(false),
261
+ /**
262
+ * Whether the caller may reply to this post (reply gating, §6). A prompt is
263
+ * repliable by any authenticated viewer (the app's audience-policy default);
264
+ * a reply only by its branch participants (`{ creator, branch responder }`).
265
+ */
266
+ canReply: z.boolean().default(false),
267
+ /** Why `canReply` is false, when it is (omitted when the caller can reply). */
268
+ replyDisabledReason: z.enum(["unauthenticated", "not_a_participant"]).optional()
269
+ });
270
+ var PostRecordPublicSchema = z.object({
271
+ text: z.string(),
272
+ title: z.string().optional(),
273
+ reply: ReplyRefSchema.optional(),
274
+ langs: z.array(z.string()).optional(),
275
+ selfLabels: z.array(z.string()).optional(),
276
+ createdAt: FirestoreTimestampSchema
277
+ });
278
+ var AudioPostViewSchema = z.object({
279
+ /** at:// URI (or internal ref) identifying the post. */
280
+ uri: z.string(),
281
+ /** Content CID of the canonical record (see `AudioPostRecordSchema.cid`). */
282
+ cid: z.string(),
283
+ kind: z.enum(["prompt", "reply"]),
284
+ /** The acting actor's app-scoped id — an opaque attribution ref, not a profile. */
285
+ authorId: z.string(),
286
+ /** The author's app-asserted AT Protocol DID, when the caller provided one. */
287
+ authorDid: z.string().optional(),
288
+ record: PostRecordPublicSchema,
289
+ /** Hydrated audio embed (signed URL + lifted transcript). */
290
+ embed: AudioEmbedViewSchema.optional(),
291
+ viewer: ViewerStateSchema
292
+ });
293
+
294
+ export { ActorProfileRecordSchema, AudioEmbedSchema, AudioEmbedViewSchema, AudioPostRecordSchema, AudioPostViewSchema, BYTE_MUTATING_STAGES, DERIVED_STAGES, PROCESSING_STAGES, PostRecordPublicSchema, ProcessingRequestSchema, ProcessingStageMapSchema, ProcessingStageSchema, ProcessingStageStatusSchema, ProcessingStateSchema, ProcessingViewSchema, ReplyRefSchema, ResolvedProcessingSchema, StrongRefSchema, TimedTranscriptSchema, TranscriptEnrichmentRecordSchema, TranscriptSegmentSchema, ViewerStateSchema, resolveAudioVariant, toProcessingView };
@@ -1,11 +1,314 @@
1
- export { CreateAudioPostRequest, CreateAudioPostRequestSchema, UpdateProfileRequestSchema } from './api-codecs.js';
2
- export { FirestoreTimestamp, FirestoreTimestampSchema, UserRecord, UserRecordSchema, httpsUrl } from './types/records.js';
3
- export { ProfileView, ProfileViewAdmin, ProfileViewAdminSchema, ProfileViewBasic, ProfileViewBasicSchema, ProfileViewDetailed, ProfileViewDetailedSchema, ProfileViewSchema, ProfileViewSelf, ProfileViewSelfSchema, toProfileViewBasic } from './types/views.js';
1
+ export { CreateAudioPostRequest, CreateAudioPostRequestSchema, PatchAudioPostRequest, PatchAudioPostRequestSchema } from './api-codecs.js';
2
+ export { FirestoreTimestamp, FirestoreTimestampSchema } from './types/records.js';
4
3
  export { ActorProfileRecord, ActorProfileRecordSchema, AudioEmbed, AudioEmbedSchema, AudioEmbedView, AudioEmbedViewSchema, AudioPostRecord, AudioPostRecordSchema, AudioPostView, AudioPostViewSchema, PostRecordPublic, PostRecordPublicSchema, ReplyRef, ReplyRefSchema, StrongRef, StrongRefSchema, TimedTranscript, TimedTranscriptSchema, TranscriptEnrichmentRecord, TranscriptEnrichmentRecordSchema, TranscriptSegment, TranscriptSegmentSchema, ViewerState, ViewerStateSchema } from './types/audio.js';
5
- export { BlobRef, BlobRefSchema, resolveAudioUrl } from './types/blob.js';
6
- export { ActorView, ActorViewSchema, PublicProfileDto, PublicProfileDtoSchema } from './types/api.js';
7
- export { COLLECTIONS, EMBED_NSID, NSID, NsidValue, nsidForCollection } from './nsid.js';
4
+ import { z } from 'zod';
5
+ export { BlobRef, BlobRefSchema } from './types/blob.js';
6
+ export { COLLECTIONS, EMBED_NSID, NSID, NsidValue, StoredNsidValue } from './nsid.js';
8
7
  export { ConflictError, ForbiddenError, NotFoundError, RateLimitError, ServiceError, UnauthorizedError, ValidationError } from './errors/index.js';
9
8
  export { isFirestoreTimestamp } from './utils/index.js';
10
9
  export { ReportedErrorEvent, buildReportedErrorEvent, reportError } from './observability/report-error.js';
11
- import 'zod';
10
+
11
+ /**
12
+ * Audio hygiene / enrichment processing (B5).
13
+ *
14
+ * Antiphony can, when the calling app opts in, run audio processing on a
15
+ * post's audio. Four stages, classified on two axes (see
16
+ * `specs/enrichment-pipeline.md`):
17
+ *
18
+ * - **Byte-mutating** (`denoise`, `trim`) produce new audio. They compose in
19
+ * order into a SINGLE processed variant — trimmed-and-denoised audio is one
20
+ * artifact, not two — addressed by `processedBlobCid`.
21
+ * - **Derived** (`transcribe`, `waveform`) are pure analysis over the final
22
+ * variant and modify no audio. Because they are a function of the variant,
23
+ * recomputation is always the correct response to their input changing.
24
+ *
25
+ * Every stage is OFF by default — the app asks for them per post via
26
+ * `CreateAudioPostRequest.processing`, or after the fact via the `processing`
27
+ * opt-in on `PATCH /api/v1/posts/{postId}`.
28
+ *
29
+ * The work runs asynchronously (outside the create request), so a post
30
+ * carries a mutable `processing` state that starts `pending` and settles to
31
+ * `ready`/`failed`/`skipped` per stage. That state is a storage-layer field —
32
+ * it is NOT part of the canonical lexicon record and never enters the record
33
+ * CID (like `kind` and `threadParticipants`), so processing can update it
34
+ * without changing the post's content address.
35
+ *
36
+ * That immutability is why stage OUTPUT lives here rather than on the embed:
37
+ * `embed.audio.ref.$link`, `embed.durationMs`, and `embed.waveform` are inside
38
+ * the CID and can never be rewritten. The read-time view resolves per field
39
+ * between the record's canonical values and the variant values below.
40
+ */
41
+ /**
42
+ * Per-stage status.
43
+ * - `pending` — requested, not yet done (the worker acts on these).
44
+ * - `ready` — completed.
45
+ * - `failed` — attempted and errored.
46
+ * - `skipped` — requested but this deployment has no provider for it.
47
+ *
48
+ * A stage returning to `pending` after having been `ready` is NORMAL, not a
49
+ * regression: a byte-mutating stage completing invalidates derived artifacts,
50
+ * which are then recomputed. Clients treat any `pending` stage as "still
51
+ * working", which already covers this.
52
+ */
53
+ declare const ProcessingStageStatusSchema: z.ZodEnum<["pending", "ready", "failed", "skipped"]>;
54
+ type ProcessingStageStatus = z.infer<typeof ProcessingStageStatusSchema>;
55
+ /**
56
+ * The stage names, in the order a multi-stage request runs them:
57
+ * denoise → trim → (transcribe, waveform).
58
+ *
59
+ * All byte-mutating stages run first; the two derived stages then consume the
60
+ * final variant and are mutually independent. Denoise precedes trim
61
+ * deliberately — silence detection keys off a noise floor, so on noisy input
62
+ * the "silence" is not actually quiet and trim under-cuts.
63
+ */
64
+ declare const PROCESSING_STAGES: readonly ["denoise", "trim", "transcribe", "waveform"];
65
+ declare const ProcessingStageSchema: z.ZodEnum<["denoise", "trim", "transcribe", "waveform"]>;
66
+ type ProcessingStage = z.infer<typeof ProcessingStageSchema>;
67
+ /** Stages that produce new audio bytes, composing into one processed variant. */
68
+ declare const BYTE_MUTATING_STAGES: readonly ["denoise", "trim"];
69
+ /** Stages that are pure analysis over the final variant, modifying no audio. */
70
+ declare const DERIVED_STAGES: readonly ["transcribe", "waveform"];
71
+ /**
72
+ * What the calling app opts into, on `CreateAudioPostRequest`. All default
73
+ * off; only `true` values request a stage.
74
+ */
75
+ declare const ProcessingRequestSchema: z.ZodObject<{
76
+ transcribe: z.ZodOptional<z.ZodBoolean>;
77
+ denoise: z.ZodOptional<z.ZodBoolean>;
78
+ trim: z.ZodOptional<z.ZodBoolean>;
79
+ waveform: z.ZodOptional<z.ZodBoolean>;
80
+ /**
81
+ * Whether a completed byte-mutating stage should invalidate and recompute
82
+ * the derived artifacts that describe the old audio. Defaults to **true**
83
+ * — a transcript of superseded audio is wrong, not merely stale.
84
+ *
85
+ * `false` opts out, for an app that would rather keep the existing
86
+ * transcript than pay to regenerate it. It does NOT name a stage, so a
87
+ * request carrying only `reprocess` requests no work.
88
+ */
89
+ reprocess: z.ZodOptional<z.ZodBoolean>;
90
+ }, "strip", z.ZodTypeAny, {
91
+ denoise?: boolean | undefined;
92
+ trim?: boolean | undefined;
93
+ transcribe?: boolean | undefined;
94
+ waveform?: boolean | undefined;
95
+ reprocess?: boolean | undefined;
96
+ }, {
97
+ denoise?: boolean | undefined;
98
+ trim?: boolean | undefined;
99
+ transcribe?: boolean | undefined;
100
+ waveform?: boolean | undefined;
101
+ reprocess?: boolean | undefined;
102
+ }>;
103
+ type ProcessingRequest = z.infer<typeof ProcessingRequestSchema>;
104
+ /**
105
+ * Per-stage status across all stages — the shape shared by the stored state,
106
+ * the hydrated view, and the resolved-initial-state handoff between the route
107
+ * and the service. A key is present iff that stage was requested.
108
+ */
109
+ declare const ProcessingStageMapSchema: z.ZodObject<{
110
+ transcribe: z.ZodOptional<z.ZodEnum<["pending", "ready", "failed", "skipped"]>>;
111
+ denoise: z.ZodOptional<z.ZodEnum<["pending", "ready", "failed", "skipped"]>>;
112
+ trim: z.ZodOptional<z.ZodEnum<["pending", "ready", "failed", "skipped"]>>;
113
+ waveform: z.ZodOptional<z.ZodEnum<["pending", "ready", "failed", "skipped"]>>;
114
+ }, "strip", z.ZodTypeAny, {
115
+ denoise?: "pending" | "ready" | "failed" | "skipped" | undefined;
116
+ trim?: "pending" | "ready" | "failed" | "skipped" | undefined;
117
+ transcribe?: "pending" | "ready" | "failed" | "skipped" | undefined;
118
+ waveform?: "pending" | "ready" | "failed" | "skipped" | undefined;
119
+ }, {
120
+ denoise?: "pending" | "ready" | "failed" | "skipped" | undefined;
121
+ trim?: "pending" | "ready" | "failed" | "skipped" | undefined;
122
+ transcribe?: "pending" | "ready" | "failed" | "skipped" | undefined;
123
+ waveform?: "pending" | "ready" | "failed" | "skipped" | undefined;
124
+ }>;
125
+ type ProcessingStageMap = z.infer<typeof ProcessingStageMapSchema>;
126
+ /**
127
+ * An opt-in request resolved against a deployment's capabilities: the initial
128
+ * per-stage state plus the settings the async worker needs to honour it.
129
+ *
130
+ * `reprocess` is carried here — and persisted — rather than passed to the
131
+ * worker as an argument, because the request that asks for the work and the
132
+ * pass that performs it are separated by a queue (step 8). Written on every
133
+ * request including the default, because the stored state is MERGED onto —
134
+ * absent means true only for posts written before this field existed.
135
+ */
136
+ declare const ResolvedProcessingSchema: z.ZodObject<{
137
+ transcribe: z.ZodOptional<z.ZodEnum<["pending", "ready", "failed", "skipped"]>>;
138
+ denoise: z.ZodOptional<z.ZodEnum<["pending", "ready", "failed", "skipped"]>>;
139
+ trim: z.ZodOptional<z.ZodEnum<["pending", "ready", "failed", "skipped"]>>;
140
+ waveform: z.ZodOptional<z.ZodEnum<["pending", "ready", "failed", "skipped"]>>;
141
+ } & {
142
+ reprocess: z.ZodOptional<z.ZodBoolean>;
143
+ }, "strip", z.ZodTypeAny, {
144
+ denoise?: "pending" | "ready" | "failed" | "skipped" | undefined;
145
+ trim?: "pending" | "ready" | "failed" | "skipped" | undefined;
146
+ transcribe?: "pending" | "ready" | "failed" | "skipped" | undefined;
147
+ waveform?: "pending" | "ready" | "failed" | "skipped" | undefined;
148
+ reprocess?: boolean | undefined;
149
+ }, {
150
+ denoise?: "pending" | "ready" | "failed" | "skipped" | undefined;
151
+ trim?: "pending" | "ready" | "failed" | "skipped" | undefined;
152
+ transcribe?: "pending" | "ready" | "failed" | "skipped" | undefined;
153
+ waveform?: "pending" | "ready" | "failed" | "skipped" | undefined;
154
+ reprocess?: boolean | undefined;
155
+ }>;
156
+ type ResolvedProcessing = z.infer<typeof ResolvedProcessingSchema>;
157
+ /**
158
+ * Stored processing state on the post record (storage-layer; not in the CID):
159
+ * the per-stage statuses plus the output of the stages themselves.
160
+ *
161
+ * The variant fields below all exist for the same reason — their canonical
162
+ * counterparts live inside the record CID and cannot be updated:
163
+ *
164
+ * - `processedBlobCid` ↔ `embed.audio.ref.$link`
165
+ * - `processedMimeType` ↔ `embed.audio.mimeType` (providers may transcode)
166
+ * - `processedDurationMs` ↔ `embed.durationMs` (trim changes duration)
167
+ * - `waveformPeaks` ↔ `embed.waveform` (the client's peaks describe the original)
168
+ */
169
+ declare const ProcessingStateSchema: z.ZodObject<{
170
+ transcribe: z.ZodOptional<z.ZodEnum<["pending", "ready", "failed", "skipped"]>>;
171
+ denoise: z.ZodOptional<z.ZodEnum<["pending", "ready", "failed", "skipped"]>>;
172
+ trim: z.ZodOptional<z.ZodEnum<["pending", "ready", "failed", "skipped"]>>;
173
+ waveform: z.ZodOptional<z.ZodEnum<["pending", "ready", "failed", "skipped"]>>;
174
+ } & {
175
+ reprocess: z.ZodOptional<z.ZodBoolean>;
176
+ } & {
177
+ /**
178
+ * Content CID of the processed audio variant — the composed output of every
179
+ * byte-mutating stage that has completed. The record's own
180
+ * `embed.audio.ref.$link` stays the ORIGINAL CID (immutable content
181
+ * address); only the read-time view swaps playback to this variant.
182
+ */
183
+ processedBlobCid: z.ZodOptional<z.ZodString>;
184
+ /**
185
+ * MIME type of the processed variant. Present because providers may
186
+ * TRANSCODE — the ElevenLabs Voice Isolator returns MP3 regardless of what
187
+ * it is given — so the variant's type cannot be assumed to match
188
+ * `embed.audio.mimeType`. Anything reading the variant's bytes must use
189
+ * this, not the embed's.
190
+ */
191
+ processedMimeType: z.ZodOptional<z.ZodString>;
192
+ /**
193
+ * Duration of the processed variant, when a byte-mutating stage changed it
194
+ * (i.e. trim). Absent when the variant's duration matches the original.
195
+ */
196
+ processedDurationMs: z.ZodOptional<z.ZodNumber>;
197
+ /**
198
+ * Peaks for the processed variant, once the `waveform` stage completes.
199
+ * Same normalization and bounds as `embed.waveform` (0–100, max 1000), so
200
+ * a view can never carry a larger payload than the record allows.
201
+ */
202
+ waveformPeaks: z.ZodOptional<z.ZodArray<z.ZodNumber, "many">>;
203
+ /**
204
+ * When the current runner's exclusive claim on this post expires.
205
+ *
206
+ * Queue delivery is at-least-once, so the same job can arrive twice and
207
+ * run CONCURRENTLY. `process()` is idempotent under sequential retry — it
208
+ * acts on `pending` and re-does nothing already settled — but two passes
209
+ * interleaved is a different failure: both read the same `pending` state,
210
+ * both bill the provider for the same stage, and both write
211
+ * `processedBlobCid`, so the surviving variant is whichever finished last
212
+ * and the other's blob is orphaned.
213
+ *
214
+ * A runner claims this field transactionally before doing any work and
215
+ * clears it when finished; a second runner finding it unexpired declines
216
+ * and returns. It is an EXPIRY, not a boolean lock, because the holder can
217
+ * die mid-run (instance recycled, process killed) with no chance to
218
+ * release — a plain flag would strand the post permanently, where a lapsed
219
+ * lease lets the next delivery pick it up.
220
+ *
221
+ * Internal, like the variant fields above: `toProcessingView` projects
222
+ * stages only, so this never reaches a client.
223
+ */
224
+ leaseUntil: z.ZodOptional<z.ZodEffects<z.ZodUnion<[z.ZodType<unknown, z.ZodTypeDef, unknown>, z.ZodString, z.ZodNumber, z.ZodDate]>, Date, unknown>>;
225
+ updatedAt: z.ZodEffects<z.ZodUnion<[z.ZodType<unknown, z.ZodTypeDef, unknown>, z.ZodString, z.ZodNumber, z.ZodDate]>, Date, unknown>;
226
+ }, "strip", z.ZodTypeAny, {
227
+ updatedAt: Date;
228
+ denoise?: "pending" | "ready" | "failed" | "skipped" | undefined;
229
+ trim?: "pending" | "ready" | "failed" | "skipped" | undefined;
230
+ transcribe?: "pending" | "ready" | "failed" | "skipped" | undefined;
231
+ waveform?: "pending" | "ready" | "failed" | "skipped" | undefined;
232
+ reprocess?: boolean | undefined;
233
+ processedBlobCid?: string | undefined;
234
+ processedMimeType?: string | undefined;
235
+ processedDurationMs?: number | undefined;
236
+ waveformPeaks?: number[] | undefined;
237
+ leaseUntil?: Date | undefined;
238
+ }, {
239
+ denoise?: "pending" | "ready" | "failed" | "skipped" | undefined;
240
+ trim?: "pending" | "ready" | "failed" | "skipped" | undefined;
241
+ transcribe?: "pending" | "ready" | "failed" | "skipped" | undefined;
242
+ waveform?: "pending" | "ready" | "failed" | "skipped" | undefined;
243
+ reprocess?: boolean | undefined;
244
+ processedBlobCid?: string | undefined;
245
+ processedMimeType?: string | undefined;
246
+ processedDurationMs?: number | undefined;
247
+ waveformPeaks?: number[] | undefined;
248
+ leaseUntil?: unknown;
249
+ updatedAt?: unknown;
250
+ }>;
251
+ type ProcessingState = z.infer<typeof ProcessingStateSchema>;
252
+ /**
253
+ * The processing status surfaced on the hydrated view — the per-stage status
254
+ * only (no internal storage fields: variant CID, duration, peaks, timestamps).
255
+ * Absent when no processing was requested.
256
+ */
257
+ declare const ProcessingViewSchema: z.ZodObject<{
258
+ transcribe: z.ZodOptional<z.ZodEnum<["pending", "ready", "failed", "skipped"]>>;
259
+ denoise: z.ZodOptional<z.ZodEnum<["pending", "ready", "failed", "skipped"]>>;
260
+ trim: z.ZodOptional<z.ZodEnum<["pending", "ready", "failed", "skipped"]>>;
261
+ waveform: z.ZodOptional<z.ZodEnum<["pending", "ready", "failed", "skipped"]>>;
262
+ }, "strip", z.ZodTypeAny, {
263
+ denoise?: "pending" | "ready" | "failed" | "skipped" | undefined;
264
+ trim?: "pending" | "ready" | "failed" | "skipped" | undefined;
265
+ transcribe?: "pending" | "ready" | "failed" | "skipped" | undefined;
266
+ waveform?: "pending" | "ready" | "failed" | "skipped" | undefined;
267
+ }, {
268
+ denoise?: "pending" | "ready" | "failed" | "skipped" | undefined;
269
+ trim?: "pending" | "ready" | "failed" | "skipped" | undefined;
270
+ transcribe?: "pending" | "ready" | "failed" | "skipped" | undefined;
271
+ waveform?: "pending" | "ready" | "failed" | "skipped" | undefined;
272
+ }>;
273
+ type ProcessingView = z.infer<typeof ProcessingViewSchema>;
274
+ /**
275
+ * Project the stored state onto the view — drops every internal field.
276
+ *
277
+ * Derived from `PROCESSING_STAGES` rather than listing the stages by hand, so
278
+ * a stage added to the set cannot be silently omitted from the view (which
279
+ * would leave clients unable to tell "not requested" from "in progress").
280
+ */
281
+ declare function toProcessingView(state: ProcessingState): ProcessingView;
282
+ /** The record's own audio fields — canonical, inside the CID, never rewritten. */
283
+ interface CanonicalAudioFields {
284
+ blobCid: string;
285
+ durationMs?: number;
286
+ waveform?: number[];
287
+ }
288
+ /**
289
+ * Resolve the audio fields a reader should see: canonical from the record,
290
+ * variant from `ProcessingState` wherever processing has superseded it.
291
+ *
292
+ * The three fields have to move together. Peaks are rendered ACROSS a duration
293
+ * and a duration describes a specific set of bytes, so serving a processed URL
294
+ * beside the original duration puts a scrubber out of alignment with the audio
295
+ * under it — the failure this function exists to prevent.
296
+ *
297
+ * Resolution is not a uniform `??` per field, because the state's fields do not
298
+ * all mean the same thing when absent:
299
+ *
300
+ * - `processedDurationMs` absent is DEFINED as "the variant's duration equals
301
+ * the original" (denoise transcodes without retiming), so falling back to
302
+ * the record there is correct, not a guess.
303
+ * - `waveformPeaks` carries no such guarantee. Recompute marks the stage
304
+ * `pending` without clearing the field, so between a variant change and the
305
+ * recomputed peaks landing it holds peaks for the SUPERSEDED variant. Hence
306
+ * the status gate rather than a presence check.
307
+ *
308
+ * Peaks do not key off `processedBlobCid`: `waveform` always runs over the
309
+ * final variant, so when it is `ready` its peaks describe whatever playback
310
+ * resolves to here, variant or original alike.
311
+ */
312
+ declare function resolveAudioVariant(canonical: CanonicalAudioFields, state: ProcessingState | undefined): CanonicalAudioFields;
313
+
314
+ export { BYTE_MUTATING_STAGES, type CanonicalAudioFields, DERIVED_STAGES, PROCESSING_STAGES, type ProcessingRequest, ProcessingRequestSchema, type ProcessingStage, type ProcessingStageMap, ProcessingStageMapSchema, ProcessingStageSchema, type ProcessingStageStatus, ProcessingStageStatusSchema, type ProcessingState, ProcessingStateSchema, type ProcessingView, ProcessingViewSchema, type ResolvedProcessing, ResolvedProcessingSchema, resolveAudioVariant, toProcessingView };
package/dist/esm/index.js CHANGED
@@ -1,12 +1,10 @@
1
1
  export { isFirestoreTimestamp } from './chunk-24KIXQZK.js';
2
- export { CreateAudioPostRequestSchema, UpdateProfileRequestSchema } from './chunk-FDM5FDN4.js';
3
- export { COLLECTIONS, EMBED_NSID, NSID, nsidForCollection } from './chunk-6LROFBKK.js';
2
+ export { CreateAudioPostRequestSchema, PatchAudioPostRequestSchema } from './chunk-F5ZKFJL2.js';
3
+ export { COLLECTIONS, EMBED_NSID, NSID } from './chunk-SBYNIQRZ.js';
4
4
  export { ConflictError, ForbiddenError, NotFoundError, RateLimitError, ServiceError, UnauthorizedError, ValidationError } from './chunk-7LW2FHLD.js';
5
5
  import './chunk-72G3LBUQ.js';
6
6
  export { buildReportedErrorEvent, reportError } from './chunk-BNNLHRH7.js';
7
- export { ActorViewSchema, PublicProfileDtoSchema } from './chunk-Y6HNNRVD.js';
8
- export { ActorProfileRecordSchema, AudioEmbedSchema, AudioEmbedViewSchema, AudioPostRecordSchema, AudioPostViewSchema, PostRecordPublicSchema, ReplyRefSchema, StrongRefSchema, TimedTranscriptSchema, TranscriptEnrichmentRecordSchema, TranscriptSegmentSchema, ViewerStateSchema } from './chunk-F7IHVM34.js';
9
- export { BlobRefSchema, resolveAudioUrl } from './chunk-5EHV73BA.js';
10
- export { ProfileViewAdminSchema, ProfileViewBasicSchema, ProfileViewDetailedSchema, ProfileViewSchema, ProfileViewSelfSchema, toProfileViewBasic } from './chunk-R6SBR3IG.js';
11
- export { FirestoreTimestampSchema, UserRecordSchema, httpsUrl } from './chunk-IHIBHQBJ.js';
7
+ export { ActorProfileRecordSchema, AudioEmbedSchema, AudioEmbedViewSchema, AudioPostRecordSchema, AudioPostViewSchema, BYTE_MUTATING_STAGES, DERIVED_STAGES, PROCESSING_STAGES, PostRecordPublicSchema, ProcessingRequestSchema, ProcessingStageMapSchema, ProcessingStageSchema, ProcessingStageStatusSchema, ProcessingStateSchema, ProcessingViewSchema, ReplyRefSchema, ResolvedProcessingSchema, StrongRefSchema, TimedTranscriptSchema, TranscriptEnrichmentRecordSchema, TranscriptSegmentSchema, ViewerStateSchema, resolveAudioVariant, toProcessingView } from './chunk-TVOXIQKF.js';
8
+ export { FirestoreTimestampSchema } from './chunk-D655OH2I.js';
9
+ export { BlobRefSchema } from './chunk-SMK4OZNU.js';
12
10
  import './chunk-5JBD5THX.js';
@@ -13,6 +13,8 @@ declare const NSID: {
13
13
  readonly ActorProfile: "dev.antiphony.actor.profile";
14
14
  };
15
15
  type NsidValue = typeof NSID[keyof typeof NSID];
16
+ /** The subset of record NSIDs the core actually stores. */
17
+ type StoredNsidValue = typeof NSID.AudioPost | typeof NSID.AudioTranscript;
16
18
  /**
17
19
  * Antiphony embed NSIDs. Embeds live inline on a post's `embed` field, not in
18
20
  * their own collection — kept out of `NSID`/`COLLECTIONS`.
@@ -22,13 +24,10 @@ declare const EMBED_NSID: {
22
24
  readonly RecordWithAudio: "dev.antiphony.embed.recordWithAudio";
23
25
  };
24
26
  /**
25
- * Maps AT Protocol record-type NSIDs to Firestore collection names.
26
- * When migrating to a PDS, this mapping becomes the adapter layer.
27
+ * Maps the STORED AT Protocol record-type NSIDs to Firestore collection
28
+ * names. When migrating to a PDS, this mapping becomes the adapter layer.
29
+ * `actor.profile` is deliberately absent — portable schema, no core storage.
27
30
  */
28
- declare const COLLECTIONS: Record<NsidValue, string>;
29
- /**
30
- * Reverse lookup: get the NSID for a Firestore collection name.
31
- */
32
- declare function nsidForCollection(collection: string): NsidValue | undefined;
31
+ declare const COLLECTIONS: Record<StoredNsidValue, string>;
33
32
 
34
- export { COLLECTIONS, EMBED_NSID, NSID, type NsidValue, nsidForCollection };
33
+ export { COLLECTIONS, EMBED_NSID, NSID, type NsidValue, type StoredNsidValue };