poddrill 0.0.0-stage → 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/dist/audit.js ADDED
@@ -0,0 +1,834 @@
1
+ import { randomBytes } from "node:crypto";
2
+ import { readFile, rename, rm, writeFile } from "node:fs/promises";
3
+ import { asRecord, errorCode, pathExists } from "./disk.js";
4
+ import { artifactKinds, episodeLayout, parseEpisodeId, } from "./workspace.js";
5
+ const artifactStates = ["waiting", "queued", "running", "ready", "failed"];
6
+ const unreadable = "the audit could not be read";
7
+ /** One chain per audit file so two appends cannot tear a line. */
8
+ const auditWrites = new Map();
9
+ export function auditRecord(call, response, at) {
10
+ const body = asRecord(response);
11
+ if (body === undefined) {
12
+ return undefined;
13
+ }
14
+ switch (call.op) {
15
+ case "ping":
16
+ case "audit":
17
+ return undefined;
18
+ case "start":
19
+ return startLine(call, body, at);
20
+ case "episode":
21
+ return episodeLine(body, at);
22
+ case "cues":
23
+ return cuesLine(call, body, at);
24
+ case "labelGroups":
25
+ return labelLine(call, body, at);
26
+ case "startSummary":
27
+ return openLine(body, at);
28
+ case "setSummary":
29
+ return setLine(call, body, at);
30
+ case "putParagraph":
31
+ return paragraphLine("putParagraph", call, body, at);
32
+ case "postParagraph":
33
+ return paragraphLine("postParagraph", call, body, at);
34
+ case "summary":
35
+ return summaryLine(call, body, at);
36
+ default: {
37
+ const leftover = call;
38
+ throw new Error(`unexpected ${JSON.stringify(leftover)}`);
39
+ }
40
+ }
41
+ }
42
+ export async function appendAudit(root, episodeId, record) {
43
+ const id = parseEpisodeId(episodeId);
44
+ if (id === undefined) {
45
+ return;
46
+ }
47
+ const layout = episodeLayout(root, id);
48
+ await withAuditPath(layout.audit, async () => {
49
+ if (!(await pathExists(layout.dir))) {
50
+ return;
51
+ }
52
+ const current = await readText(layout.audit);
53
+ await replaceFile(layout.audit, `${current}${JSON.stringify(record)}\n`);
54
+ });
55
+ }
56
+ export async function readAudit(root, episodeId) {
57
+ const id = parseEpisodeId(episodeId);
58
+ if (id === undefined) {
59
+ return { state: "unknown", episodeId };
60
+ }
61
+ const layout = episodeLayout(root, id);
62
+ if (!(await pathExists(layout.dir))) {
63
+ return { state: "unknown", episodeId };
64
+ }
65
+ if (!(await pathExists(layout.audit))) {
66
+ return { state: "present", episodeId, records: [] };
67
+ }
68
+ let text;
69
+ try {
70
+ text = await readFile(layout.audit, "utf8");
71
+ }
72
+ catch {
73
+ return { kind: "error", message: unreadable };
74
+ }
75
+ const records = parseAuditText(text);
76
+ if (records === undefined) {
77
+ return { kind: "error", message: unreadable };
78
+ }
79
+ return { state: "present", episodeId, records };
80
+ }
81
+ function startLine(call, response, at) {
82
+ if (response["kind"] !== "started" || typeof response["created"] !== "boolean") {
83
+ return undefined;
84
+ }
85
+ const language = call.language === undefined ? "en" : call.language;
86
+ return {
87
+ at,
88
+ op: "start",
89
+ request: { language },
90
+ result: { kind: "started", created: response["created"] },
91
+ };
92
+ }
93
+ function episodeLine(response, at) {
94
+ const result = gated(response, () => {
95
+ if (response["state"] !== "present") {
96
+ return undefined;
97
+ }
98
+ const artifacts = episodeArtifacts(response["artifacts"]);
99
+ if (artifacts === undefined) {
100
+ return undefined;
101
+ }
102
+ return { state: "present", artifacts };
103
+ });
104
+ if (result === undefined) {
105
+ return undefined;
106
+ }
107
+ return { at, op: "episode", request: {}, result };
108
+ }
109
+ function cuesLine(call, response, at) {
110
+ const request = cuesRequest(call);
111
+ const result = gated(response, () => cuesSuccess(response));
112
+ if (result === undefined) {
113
+ return undefined;
114
+ }
115
+ return { at, op: "cues", request, result };
116
+ }
117
+ function labelLine(call, response, at) {
118
+ const result = gated(response, () => stateOnly(response));
119
+ if (result === undefined) {
120
+ return undefined;
121
+ }
122
+ return { at, op: "labelGroups", request: { groups: labelPairs(call.groups) }, result };
123
+ }
124
+ function openLine(response, at) {
125
+ const result = gated(response, () => openSuccess(response));
126
+ if (result === undefined) {
127
+ return undefined;
128
+ }
129
+ return { at, op: "startSummary", request: {}, result };
130
+ }
131
+ function setLine(call, response, at) {
132
+ const result = gated(response, () => stateOnly(response));
133
+ if (result === undefined) {
134
+ return undefined;
135
+ }
136
+ return { at, op: "setSummary", request: setRequest(call.summaryId, call.summary), result };
137
+ }
138
+ function paragraphLine(op, call, response, at) {
139
+ const result = gated(response, () => paragraphSuccess(response, call.body));
140
+ if (result === undefined) {
141
+ return undefined;
142
+ }
143
+ return { at, op, request: paragraphRequest(call.summaryId, call.body), result };
144
+ }
145
+ function summaryLine(call, response, at) {
146
+ const result = gated(response, () => stateOnly(response));
147
+ if (result === undefined) {
148
+ return undefined;
149
+ }
150
+ return { at, op: "summary", request: { summaryId: call.summaryId }, result };
151
+ }
152
+ function gated(response, success) {
153
+ const decision = gate(response);
154
+ switch (decision.kind) {
155
+ case "drop":
156
+ return undefined;
157
+ case "replace":
158
+ return decision.result;
159
+ case "go":
160
+ return success();
161
+ default: {
162
+ const leftover = decision;
163
+ throw new Error(`unexpected ${JSON.stringify(leftover)}`);
164
+ }
165
+ }
166
+ }
167
+ function gate(response) {
168
+ if (response["kind"] === "error") {
169
+ return typeof response["message"] === "string"
170
+ ? { kind: "replace", result: { kind: "error", message: response["message"] } }
171
+ : { kind: "drop" };
172
+ }
173
+ if (response["kind"] === "rejected") {
174
+ return typeof response["reason"] === "string"
175
+ ? { kind: "replace", result: { kind: "rejected", reason: response["reason"] } }
176
+ : { kind: "drop" };
177
+ }
178
+ if (response["state"] === "not-ready") {
179
+ return isArtifactKind(response["on"])
180
+ ? { kind: "replace", result: { state: "not-ready", on: response["on"] } }
181
+ : { kind: "drop" };
182
+ }
183
+ if (unknownEpisode(response)) {
184
+ return { kind: "drop" };
185
+ }
186
+ return { kind: "go" };
187
+ }
188
+ function unknownEpisode(response) {
189
+ return (response["state"] === "unknown" &&
190
+ response["groupId"] === undefined &&
191
+ response["cueId"] === undefined &&
192
+ response["summaryId"] === undefined);
193
+ }
194
+ function cuesRequest(call) {
195
+ return {
196
+ ...(call.limit === undefined ? {} : { limit: call.limit }),
197
+ ...(call.after === undefined ? {} : { after: call.after }),
198
+ ...(call.groupId === undefined ? {} : { groupId: call.groupId }),
199
+ ...(call.hosts === undefined ? {} : { hosts: call.hosts }),
200
+ };
201
+ }
202
+ function cuesSuccess(response) {
203
+ if (response["state"] === "unknown") {
204
+ return { state: "unknown" };
205
+ }
206
+ if (response["state"] !== "present") {
207
+ return undefined;
208
+ }
209
+ return cueSpan(response["cues"]);
210
+ }
211
+ function cueSpan(cues) {
212
+ if (!Array.isArray(cues)) {
213
+ return undefined;
214
+ }
215
+ const ids = [];
216
+ for (const cue of cues) {
217
+ const record = asRecord(cue);
218
+ if (record === undefined || typeof record["id"] !== "string") {
219
+ return undefined;
220
+ }
221
+ ids.push(record["id"]);
222
+ }
223
+ if (ids.length === 0) {
224
+ return { state: "present", count: 0 };
225
+ }
226
+ const first = ids[0];
227
+ const last = ids[ids.length - 1];
228
+ if (first === undefined || last === undefined) {
229
+ return undefined;
230
+ }
231
+ return { state: "present", count: ids.length, first, last };
232
+ }
233
+ function stateOnly(response) {
234
+ if (response["state"] === "present") {
235
+ return { state: "present" };
236
+ }
237
+ if (response["state"] === "unknown") {
238
+ return { state: "unknown" };
239
+ }
240
+ return undefined;
241
+ }
242
+ function labelPairs(groups) {
243
+ if (!Array.isArray(groups)) {
244
+ return [];
245
+ }
246
+ const pairs = [];
247
+ for (const item of groups) {
248
+ const record = asRecord(item);
249
+ if (record === undefined) {
250
+ continue;
251
+ }
252
+ const groupId = record["groupId"];
253
+ const label = record["label"];
254
+ if (typeof groupId !== "string" || typeof label !== "string") {
255
+ continue;
256
+ }
257
+ pairs.push({ groupId, label });
258
+ }
259
+ return pairs;
260
+ }
261
+ function openSuccess(response) {
262
+ if (response["state"] === "unknown") {
263
+ return { state: "unknown" };
264
+ }
265
+ if (response["state"] !== "present") {
266
+ return undefined;
267
+ }
268
+ if (typeof response["summaryId"] === "string") {
269
+ return { state: "present", summaryId: response["summaryId"] };
270
+ }
271
+ return { state: "present" };
272
+ }
273
+ function setRequest(summaryId, summary) {
274
+ if (typeof summary !== "string") {
275
+ return { summaryId };
276
+ }
277
+ return { summaryId, length: summary.length };
278
+ }
279
+ function paragraphRequest(summaryId, body) {
280
+ const record = asRecord(body);
281
+ const cues = record === undefined ? undefined : stringList(record["cues"]);
282
+ if (cues === undefined) {
283
+ return { summaryId };
284
+ }
285
+ return { summaryId, cues };
286
+ }
287
+ function stringList(value) {
288
+ if (!Array.isArray(value)) {
289
+ return undefined;
290
+ }
291
+ const ids = [];
292
+ for (const item of value) {
293
+ if (typeof item !== "string") {
294
+ return undefined;
295
+ }
296
+ ids.push(item);
297
+ }
298
+ return ids;
299
+ }
300
+ function paragraphSuccess(response, body) {
301
+ if (response["state"] === "unknown") {
302
+ return { state: "unknown" };
303
+ }
304
+ if (response["state"] !== "present") {
305
+ return undefined;
306
+ }
307
+ const index = writtenIndex(response, body);
308
+ if (index === undefined) {
309
+ return { state: "present" };
310
+ }
311
+ return { state: "present", index };
312
+ }
313
+ function writtenIndex(response, body) {
314
+ if (isIndex(response["index"])) {
315
+ return response["index"];
316
+ }
317
+ const paragraphs = response["paragraphs"];
318
+ if (!Array.isArray(paragraphs)) {
319
+ return undefined;
320
+ }
321
+ const requested = bodyIndex(body);
322
+ if (requested !== undefined && paragraphs.some((item) => paragraphIndex(item) === requested)) {
323
+ return requested;
324
+ }
325
+ if (paragraphs.length === 0) {
326
+ return undefined;
327
+ }
328
+ return paragraphIndex(paragraphs[paragraphs.length - 1]);
329
+ }
330
+ function bodyIndex(body) {
331
+ const record = asRecord(body);
332
+ if (record === undefined) {
333
+ return undefined;
334
+ }
335
+ return isIndex(record["index"]) ? record["index"] : undefined;
336
+ }
337
+ function paragraphIndex(value) {
338
+ const record = asRecord(value);
339
+ if (record === undefined || !isIndex(record["index"])) {
340
+ return undefined;
341
+ }
342
+ return record["index"];
343
+ }
344
+ function isIndex(value) {
345
+ return typeof value === "number" && Number.isInteger(value);
346
+ }
347
+ function episodeArtifacts(value) {
348
+ if (!Array.isArray(value)) {
349
+ return undefined;
350
+ }
351
+ const found = new Map();
352
+ for (const item of value) {
353
+ const record = asRecord(item);
354
+ if (record === undefined) {
355
+ return undefined;
356
+ }
357
+ const name = record["artifact"];
358
+ const state = record["state"];
359
+ if (!isArtifactKind(name) || !isArtifactState(state)) {
360
+ return undefined;
361
+ }
362
+ found.set(name, state);
363
+ }
364
+ const snaps = [];
365
+ for (const name of artifactKinds) {
366
+ const state = found.get(name);
367
+ if (state === undefined) {
368
+ return undefined;
369
+ }
370
+ snaps.push({ name, state });
371
+ }
372
+ return snaps;
373
+ }
374
+ function isArtifactKind(value) {
375
+ return artifactKinds.some((kind) => kind === value);
376
+ }
377
+ function isArtifactState(value) {
378
+ return artifactStates.some((state) => state === value);
379
+ }
380
+ function withAuditPath(path, work) {
381
+ const previous = auditWrites.get(path) ?? Promise.resolve();
382
+ const settled = previous.then(() => undefined, () => undefined);
383
+ const run = settled.then(work);
384
+ auditWrites.set(path, run.then(() => undefined, () => undefined));
385
+ return run;
386
+ }
387
+ async function readText(path) {
388
+ try {
389
+ return await readFile(path, "utf8");
390
+ }
391
+ catch (error) {
392
+ if (errorCode(error) === "ENOENT") {
393
+ return "";
394
+ }
395
+ throw error;
396
+ }
397
+ }
398
+ async function replaceFile(target, text) {
399
+ const temp = `${target}.${randomBytes(4).toString("hex")}.tmp`;
400
+ try {
401
+ await writeFile(temp, text);
402
+ await rename(temp, target);
403
+ }
404
+ catch (error) {
405
+ await rm(temp, { force: true });
406
+ throw error;
407
+ }
408
+ }
409
+ function parseAuditText(text) {
410
+ const parts = text.split("\n");
411
+ if (parts.length > 0 && parts[parts.length - 1] === "") {
412
+ parts.pop();
413
+ }
414
+ const records = [];
415
+ for (const part of parts) {
416
+ if (part === "") {
417
+ return undefined;
418
+ }
419
+ let parsed;
420
+ try {
421
+ parsed = JSON.parse(part);
422
+ }
423
+ catch {
424
+ return undefined;
425
+ }
426
+ const line = parseAuditLine(parsed);
427
+ if (line === undefined) {
428
+ return undefined;
429
+ }
430
+ records.push(line);
431
+ }
432
+ return records;
433
+ }
434
+ function parseAuditLine(value) {
435
+ const record = asRecord(value);
436
+ if (record === undefined || !keysAre(record, ["at", "op", "request", "result"])) {
437
+ return undefined;
438
+ }
439
+ const at = record["at"];
440
+ const op = record["op"];
441
+ if (typeof at !== "string" || at === "" || !isAuditOp(op)) {
442
+ return undefined;
443
+ }
444
+ switch (op) {
445
+ case "start":
446
+ return parseStart(at, record["request"], record["result"]);
447
+ case "episode":
448
+ return parseEpisode(at, record["request"], record["result"]);
449
+ case "cues":
450
+ return parseCues(at, record["request"], record["result"]);
451
+ case "labelGroups":
452
+ return parseLabels(at, record["request"], record["result"]);
453
+ case "startSummary":
454
+ return parseStartedSummary(at, record["request"], record["result"]);
455
+ case "openSummary":
456
+ return parseOpen(at, record["request"], record["result"]);
457
+ case "setSummary":
458
+ return parseSet(at, record["request"], record["result"]);
459
+ case "putParagraph":
460
+ return parseParagraph("putParagraph", at, record["request"], record["result"]);
461
+ case "postParagraph":
462
+ return parseParagraph("postParagraph", at, record["request"], record["result"]);
463
+ case "summary":
464
+ return parseSummary(at, record["request"], record["result"]);
465
+ default: {
466
+ const leftover = op;
467
+ throw new Error(`unexpected ${JSON.stringify(leftover)}`);
468
+ }
469
+ }
470
+ }
471
+ function parseStart(at, request, result) {
472
+ const requestRecord = asRecord(request);
473
+ const resultRecord = asRecord(result);
474
+ if (requestRecord === undefined ||
475
+ resultRecord === undefined ||
476
+ !keysAre(requestRecord, ["language"]) ||
477
+ typeof requestRecord["language"] !== "string" ||
478
+ !keysAre(resultRecord, ["kind", "created"]) ||
479
+ resultRecord["kind"] !== "started" ||
480
+ typeof resultRecord["created"] !== "boolean") {
481
+ return undefined;
482
+ }
483
+ return {
484
+ at,
485
+ op: "start",
486
+ request: { language: requestRecord["language"] },
487
+ result: { kind: "started", created: resultRecord["created"] },
488
+ };
489
+ }
490
+ function parseEpisode(at, request, result) {
491
+ const requestRecord = asRecord(request);
492
+ if (requestRecord === undefined || Object.keys(requestRecord).length !== 0) {
493
+ return undefined;
494
+ }
495
+ const parsed = parseEpisodeResult(result);
496
+ if (parsed === undefined) {
497
+ return undefined;
498
+ }
499
+ return { at, op: "episode", request: {}, result: parsed };
500
+ }
501
+ function parseEpisodeResult(result) {
502
+ const failure = parseFailure(result);
503
+ if (failure !== undefined) {
504
+ return failure;
505
+ }
506
+ const record = asRecord(result);
507
+ if (record === undefined || !keysAre(record, ["state", "artifacts"]) || record["state"] !== "present") {
508
+ return undefined;
509
+ }
510
+ const artifacts = parseSnaps(record["artifacts"]);
511
+ if (artifacts === undefined) {
512
+ return undefined;
513
+ }
514
+ return { state: "present", artifacts };
515
+ }
516
+ function parseSnaps(value) {
517
+ if (!Array.isArray(value)) {
518
+ return undefined;
519
+ }
520
+ const snaps = [];
521
+ for (const item of value) {
522
+ const record = asRecord(item);
523
+ if (record === undefined ||
524
+ !keysAre(record, ["name", "state"]) ||
525
+ !isArtifactKind(record["name"]) ||
526
+ !isArtifactState(record["state"])) {
527
+ return undefined;
528
+ }
529
+ snaps.push({ name: record["name"], state: record["state"] });
530
+ }
531
+ return snaps;
532
+ }
533
+ function parseCues(at, request, result) {
534
+ const parsedRequest = parseCuesRequest(request);
535
+ const parsedResult = parseCuesResult(result);
536
+ if (parsedRequest === undefined || parsedResult === undefined) {
537
+ return undefined;
538
+ }
539
+ return { at, op: "cues", request: parsedRequest, result: parsedResult };
540
+ }
541
+ function parseCuesRequest(value) {
542
+ const record = asRecord(value);
543
+ if (record === undefined) {
544
+ return undefined;
545
+ }
546
+ for (const key of Object.keys(record)) {
547
+ if (key !== "limit" && key !== "after" && key !== "groupId" && key !== "hosts") {
548
+ return undefined;
549
+ }
550
+ }
551
+ const limit = record["limit"];
552
+ const after = record["after"];
553
+ const groupId = record["groupId"];
554
+ const hosts = record["hosts"];
555
+ if (limit !== undefined && typeof limit !== "number") {
556
+ return undefined;
557
+ }
558
+ if (after !== undefined && typeof after !== "string") {
559
+ return undefined;
560
+ }
561
+ if (groupId !== undefined && typeof groupId !== "string") {
562
+ return undefined;
563
+ }
564
+ if (hosts !== undefined && typeof hosts !== "boolean") {
565
+ return undefined;
566
+ }
567
+ return {
568
+ ...(typeof limit === "number" ? { limit } : {}),
569
+ ...(typeof after === "string" ? { after } : {}),
570
+ ...(typeof groupId === "string" ? { groupId } : {}),
571
+ ...(typeof hosts === "boolean" ? { hosts } : {}),
572
+ };
573
+ }
574
+ function parseCuesResult(value) {
575
+ const failure = parseFailure(value);
576
+ if (failure !== undefined) {
577
+ return failure;
578
+ }
579
+ const unknown = parseUnknown(value);
580
+ if (unknown !== undefined) {
581
+ return unknown;
582
+ }
583
+ const record = asRecord(value);
584
+ if (record === undefined || record["state"] !== "present" || typeof record["count"] !== "number") {
585
+ return undefined;
586
+ }
587
+ if (!Number.isInteger(record["count"]) || record["count"] < 0) {
588
+ return undefined;
589
+ }
590
+ if (record["count"] === 0) {
591
+ if (!keysAre(record, ["state", "count"])) {
592
+ return undefined;
593
+ }
594
+ return { state: "present", count: 0 };
595
+ }
596
+ if (!keysAre(record, ["state", "count", "first", "last"]) ||
597
+ typeof record["first"] !== "string" ||
598
+ typeof record["last"] !== "string") {
599
+ return undefined;
600
+ }
601
+ return { state: "present", count: record["count"], first: record["first"], last: record["last"] };
602
+ }
603
+ function parseLabels(at, request, result) {
604
+ const requestRecord = asRecord(request);
605
+ const parsedResult = parseStateOrFailure(result);
606
+ if (requestRecord === undefined || !keysAre(requestRecord, ["groups"]) || parsedResult === undefined) {
607
+ return undefined;
608
+ }
609
+ const groups = parsePairs(requestRecord["groups"]);
610
+ if (groups === undefined) {
611
+ return undefined;
612
+ }
613
+ return { at, op: "labelGroups", request: { groups }, result: parsedResult };
614
+ }
615
+ function parsePairs(value) {
616
+ if (!Array.isArray(value)) {
617
+ return undefined;
618
+ }
619
+ const pairs = [];
620
+ for (const item of value) {
621
+ const record = asRecord(item);
622
+ if (record === undefined ||
623
+ !keysAre(record, ["groupId", "label"]) ||
624
+ typeof record["groupId"] !== "string" ||
625
+ typeof record["label"] !== "string") {
626
+ return undefined;
627
+ }
628
+ pairs.push({ groupId: record["groupId"], label: record["label"] });
629
+ }
630
+ return pairs;
631
+ }
632
+ function parseStartedSummary(at, request, result) {
633
+ const parsedRequest = parseOpenRequest(request);
634
+ const parsedResult = parseOpenResult(result);
635
+ if (parsedRequest === undefined || parsedResult === undefined || Object.keys(parsedRequest).length !== 0) {
636
+ return undefined;
637
+ }
638
+ return { at, op: "startSummary", request: {}, result: parsedResult };
639
+ }
640
+ function parseOpen(at, request, result) {
641
+ const parsedRequest = parseOpenRequest(request);
642
+ const parsedResult = parseOpenResult(result);
643
+ if (parsedRequest === undefined || parsedResult === undefined) {
644
+ return undefined;
645
+ }
646
+ return { at, op: "openSummary", request: parsedRequest, result: parsedResult };
647
+ }
648
+ function parseOpenRequest(value) {
649
+ const record = asRecord(value);
650
+ if (record === undefined) {
651
+ return undefined;
652
+ }
653
+ const type = record["type"];
654
+ const request = record["request"];
655
+ const keys = Object.keys(record);
656
+ if (keys.length === 0) {
657
+ return {};
658
+ }
659
+ // Lines written when open stored a type still name that field. New lines store request only.
660
+ if (keys.length === 2 && typeof type === "string" && typeof request === "string" && keysAre(record, ["type", "request"])) {
661
+ return { type, request };
662
+ }
663
+ if (keys.length === 1 && typeof type === "string" && keysAre(record, ["type"])) {
664
+ return { type };
665
+ }
666
+ if (keys.length === 1 && typeof request === "string" && keysAre(record, ["request"])) {
667
+ return { request };
668
+ }
669
+ return undefined;
670
+ }
671
+ function parseOpenResult(value) {
672
+ const failure = parseFailure(value);
673
+ if (failure !== undefined) {
674
+ return failure;
675
+ }
676
+ const unknown = parseUnknown(value);
677
+ if (unknown !== undefined) {
678
+ return unknown;
679
+ }
680
+ const record = asRecord(value);
681
+ if (record === undefined || record["state"] !== "present") {
682
+ return undefined;
683
+ }
684
+ if (keysAre(record, ["state", "summaryId"]) && typeof record["summaryId"] === "string") {
685
+ return { state: "present", summaryId: record["summaryId"] };
686
+ }
687
+ if (keysAre(record, ["state"])) {
688
+ return { state: "present" };
689
+ }
690
+ return undefined;
691
+ }
692
+ function parseSet(at, request, result) {
693
+ const parsedRequest = parseSetRequest(request);
694
+ const parsedResult = parseStateOrFailure(result);
695
+ if (parsedRequest === undefined || parsedResult === undefined) {
696
+ return undefined;
697
+ }
698
+ return { at, op: "setSummary", request: parsedRequest, result: parsedResult };
699
+ }
700
+ function parseSetRequest(value) {
701
+ const record = asRecord(value);
702
+ if (record === undefined || typeof record["summaryId"] !== "string") {
703
+ return undefined;
704
+ }
705
+ if (keysAre(record, ["summaryId", "length"]) && isLength(record["length"])) {
706
+ return { summaryId: record["summaryId"], length: record["length"] };
707
+ }
708
+ if (keysAre(record, ["summaryId"])) {
709
+ return { summaryId: record["summaryId"] };
710
+ }
711
+ return undefined;
712
+ }
713
+ function parseParagraph(op, at, request, result) {
714
+ const parsedRequest = parseParagraphRequest(request);
715
+ const parsedResult = parseParagraphResult(result);
716
+ if (parsedRequest === undefined || parsedResult === undefined) {
717
+ return undefined;
718
+ }
719
+ return { at, op, request: parsedRequest, result: parsedResult };
720
+ }
721
+ function parseParagraphRequest(value) {
722
+ const record = asRecord(value);
723
+ if (record === undefined || typeof record["summaryId"] !== "string") {
724
+ return undefined;
725
+ }
726
+ const summaryId = record["summaryId"];
727
+ const cues = stringList(record["cues"]);
728
+ if (keysAre(record, ["summaryId", "cues"]) && cues !== undefined) {
729
+ return { summaryId, cues };
730
+ }
731
+ if (keysAre(record, ["summaryId"])) {
732
+ return { summaryId };
733
+ }
734
+ return undefined;
735
+ }
736
+ function parseParagraphResult(value) {
737
+ const failure = parseFailure(value);
738
+ if (failure !== undefined) {
739
+ return failure;
740
+ }
741
+ const unknown = parseUnknown(value);
742
+ if (unknown !== undefined) {
743
+ return unknown;
744
+ }
745
+ const record = asRecord(value);
746
+ if (record === undefined || record["state"] !== "present") {
747
+ return undefined;
748
+ }
749
+ if (keysAre(record, ["state", "index"]) && isIndex(record["index"])) {
750
+ return { state: "present", index: record["index"] };
751
+ }
752
+ if (keysAre(record, ["state"])) {
753
+ return { state: "present" };
754
+ }
755
+ return undefined;
756
+ }
757
+ function parseSummary(at, request, result) {
758
+ const requestRecord = asRecord(request);
759
+ const parsedResult = parseStateOrFailure(result);
760
+ if (requestRecord === undefined ||
761
+ !keysAre(requestRecord, ["summaryId"]) ||
762
+ typeof requestRecord["summaryId"] !== "string" ||
763
+ parsedResult === undefined) {
764
+ return undefined;
765
+ }
766
+ return { at, op: "summary", request: { summaryId: requestRecord["summaryId"] }, result: parsedResult };
767
+ }
768
+ function parseStateOrFailure(value) {
769
+ const failure = parseFailure(value);
770
+ if (failure !== undefined) {
771
+ return failure;
772
+ }
773
+ const unknown = parseUnknown(value);
774
+ if (unknown !== undefined) {
775
+ return unknown;
776
+ }
777
+ const record = asRecord(value);
778
+ if (record === undefined || !keysAre(record, ["state"]) || record["state"] !== "present") {
779
+ return undefined;
780
+ }
781
+ return { state: "present" };
782
+ }
783
+ function parseUnknown(value) {
784
+ const record = asRecord(value);
785
+ if (record === undefined || !keysAre(record, ["state"]) || record["state"] !== "unknown") {
786
+ return undefined;
787
+ }
788
+ return { state: "unknown" };
789
+ }
790
+ function parseFailure(value) {
791
+ const record = asRecord(value);
792
+ if (record === undefined) {
793
+ return undefined;
794
+ }
795
+ if (keysAre(record, ["kind", "message"]) && record["kind"] === "error" && typeof record["message"] === "string") {
796
+ return { kind: "error", message: record["message"] };
797
+ }
798
+ if (keysAre(record, ["kind", "reason"]) && record["kind"] === "rejected" && typeof record["reason"] === "string") {
799
+ return { kind: "rejected", reason: record["reason"] };
800
+ }
801
+ if (keysAre(record, ["state", "on"]) &&
802
+ record["state"] === "not-ready" &&
803
+ isArtifactKind(record["on"])) {
804
+ return { state: "not-ready", on: record["on"] };
805
+ }
806
+ return undefined;
807
+ }
808
+ function isLength(value) {
809
+ return typeof value === "number" && Number.isInteger(value) && value >= 0;
810
+ }
811
+ function isAuditOp(value) {
812
+ switch (value) {
813
+ case "start":
814
+ case "episode":
815
+ case "cues":
816
+ case "labelGroups":
817
+ case "startSummary":
818
+ case "openSummary":
819
+ case "setSummary":
820
+ case "putParagraph":
821
+ case "postParagraph":
822
+ case "summary":
823
+ return true;
824
+ default:
825
+ return false;
826
+ }
827
+ }
828
+ function keysAre(record, keys) {
829
+ const present = Object.keys(record);
830
+ if (present.length !== keys.length) {
831
+ return false;
832
+ }
833
+ return keys.every((key) => Object.hasOwn(record, key));
834
+ }