@indigoai-us/hq-cloud 5.19.1 → 5.21.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 (128) hide show
  1. package/.github/workflows/ci.yml +8 -4
  2. package/.github/workflows/publish.yml +9 -3
  3. package/dist/bin/sync-runner.d.ts +9 -0
  4. package/dist/bin/sync-runner.d.ts.map +1 -1
  5. package/dist/bin/sync-runner.js +58 -0
  6. package/dist/bin/sync-runner.js.map +1 -1
  7. package/dist/entity-resolver.d.ts +53 -0
  8. package/dist/entity-resolver.d.ts.map +1 -0
  9. package/dist/entity-resolver.js +127 -0
  10. package/dist/entity-resolver.js.map +1 -0
  11. package/dist/entity-resolver.test.d.ts +10 -0
  12. package/dist/entity-resolver.test.d.ts.map +1 -0
  13. package/dist/entity-resolver.test.js +244 -0
  14. package/dist/entity-resolver.test.js.map +1 -0
  15. package/dist/index.d.ts +17 -1
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +21 -0
  18. package/dist/index.js.map +1 -1
  19. package/dist/schemas/signal-types.d.ts +16 -0
  20. package/dist/schemas/signal-types.d.ts.map +1 -0
  21. package/dist/schemas/signal-types.js +30 -0
  22. package/dist/schemas/signal-types.js.map +1 -0
  23. package/dist/schemas/signal-types.test.d.ts +2 -0
  24. package/dist/schemas/signal-types.test.d.ts.map +1 -0
  25. package/dist/schemas/signal-types.test.js +65 -0
  26. package/dist/schemas/signal-types.test.js.map +1 -0
  27. package/dist/schemas/source-channels.d.ts +15 -0
  28. package/dist/schemas/source-channels.d.ts.map +1 -0
  29. package/dist/schemas/source-channels.js +28 -0
  30. package/dist/schemas/source-channels.js.map +1 -0
  31. package/dist/schemas/source-channels.test.d.ts +2 -0
  32. package/dist/schemas/source-channels.test.d.ts.map +1 -0
  33. package/dist/schemas/source-channels.test.js +65 -0
  34. package/dist/schemas/source-channels.test.js.map +1 -0
  35. package/dist/signals/get.d.ts +13 -0
  36. package/dist/signals/get.d.ts.map +1 -0
  37. package/dist/signals/get.js +74 -0
  38. package/dist/signals/get.js.map +1 -0
  39. package/dist/signals/get.test.d.ts +5 -0
  40. package/dist/signals/get.test.d.ts.map +1 -0
  41. package/dist/signals/get.test.js +170 -0
  42. package/dist/signals/get.test.js.map +1 -0
  43. package/dist/signals/internals.d.ts +16 -0
  44. package/dist/signals/internals.d.ts.map +1 -0
  45. package/dist/signals/internals.js +39 -0
  46. package/dist/signals/internals.js.map +1 -0
  47. package/dist/signals/list.d.ts +10 -0
  48. package/dist/signals/list.d.ts.map +1 -0
  49. package/dist/signals/list.js +76 -0
  50. package/dist/signals/list.js.map +1 -0
  51. package/dist/signals/list.test.d.ts +9 -0
  52. package/dist/signals/list.test.d.ts.map +1 -0
  53. package/dist/signals/list.test.js +227 -0
  54. package/dist/signals/list.test.js.map +1 -0
  55. package/dist/signals/parse.d.ts +8 -0
  56. package/dist/signals/parse.d.ts.map +1 -0
  57. package/dist/signals/parse.js +8 -0
  58. package/dist/signals/parse.js.map +1 -0
  59. package/dist/signals/types.d.ts +69 -0
  60. package/dist/signals/types.d.ts.map +1 -0
  61. package/dist/signals/types.js +10 -0
  62. package/dist/signals/types.js.map +1 -0
  63. package/dist/sources/get.d.ts +11 -0
  64. package/dist/sources/get.d.ts.map +1 -0
  65. package/dist/sources/get.js +67 -0
  66. package/dist/sources/get.js.map +1 -0
  67. package/dist/sources/get.test.d.ts +5 -0
  68. package/dist/sources/get.test.d.ts.map +1 -0
  69. package/dist/sources/get.test.js +132 -0
  70. package/dist/sources/get.test.js.map +1 -0
  71. package/dist/sources/internals.d.ts +16 -0
  72. package/dist/sources/internals.d.ts.map +1 -0
  73. package/dist/sources/internals.js +39 -0
  74. package/dist/sources/internals.js.map +1 -0
  75. package/dist/sources/list.d.ts +10 -0
  76. package/dist/sources/list.d.ts.map +1 -0
  77. package/dist/sources/list.js +76 -0
  78. package/dist/sources/list.js.map +1 -0
  79. package/dist/sources/list.test.d.ts +8 -0
  80. package/dist/sources/list.test.d.ts.map +1 -0
  81. package/dist/sources/list.test.js +198 -0
  82. package/dist/sources/list.test.js.map +1 -0
  83. package/dist/sources/parse.d.ts +18 -0
  84. package/dist/sources/parse.d.ts.map +1 -0
  85. package/dist/sources/parse.js +35 -0
  86. package/dist/sources/parse.js.map +1 -0
  87. package/dist/sources/types.d.ts +62 -0
  88. package/dist/sources/types.d.ts.map +1 -0
  89. package/dist/sources/types.js +8 -0
  90. package/dist/sources/types.js.map +1 -0
  91. package/dist/telemetry.d.ts +87 -0
  92. package/dist/telemetry.d.ts.map +1 -0
  93. package/dist/telemetry.js +349 -0
  94. package/dist/telemetry.js.map +1 -0
  95. package/dist/telemetry.test.d.ts +11 -0
  96. package/dist/telemetry.test.d.ts.map +1 -0
  97. package/dist/telemetry.test.js +309 -0
  98. package/dist/telemetry.test.js.map +1 -0
  99. package/dist/vault-client.d.ts +43 -0
  100. package/dist/vault-client.d.ts.map +1 -1
  101. package/dist/vault-client.js +28 -0
  102. package/dist/vault-client.js.map +1 -1
  103. package/package.json +5 -3
  104. package/src/bin/sync-runner.ts +73 -0
  105. package/src/entity-resolver.test.ts +315 -0
  106. package/src/entity-resolver.ts +180 -0
  107. package/src/index.ts +76 -0
  108. package/src/schemas/signal-types.test.ts +82 -0
  109. package/src/schemas/signal-types.ts +38 -0
  110. package/src/schemas/source-channels.test.ts +82 -0
  111. package/src/schemas/source-channels.ts +36 -0
  112. package/src/signals/get.test.ts +204 -0
  113. package/src/signals/get.ts +79 -0
  114. package/src/signals/internals.ts +46 -0
  115. package/src/signals/list.test.ts +283 -0
  116. package/src/signals/list.ts +92 -0
  117. package/src/signals/parse.ts +8 -0
  118. package/src/signals/types.ts +74 -0
  119. package/src/sources/get.test.ts +166 -0
  120. package/src/sources/get.ts +75 -0
  121. package/src/sources/internals.ts +46 -0
  122. package/src/sources/list.test.ts +247 -0
  123. package/src/sources/list.ts +95 -0
  124. package/src/sources/parse.ts +43 -0
  125. package/src/sources/types.ts +67 -0
  126. package/src/telemetry.test.ts +394 -0
  127. package/src/telemetry.ts +436 -0
  128. package/src/vault-client.ts +60 -0
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Internal helpers shared between sources/list.ts and sources/get.ts.
3
+ *
4
+ * The S3 client factory hook exists so tests can inject an in-memory stub
5
+ * without standing up vi.mock for @aws-sdk/client-s3. Production code never
6
+ * calls the setter.
7
+ */
8
+
9
+ import { S3Client } from "@aws-sdk/client-s3";
10
+ import type { EntityContext } from "../types.js";
11
+
12
+ function defaultFactory(ctx: EntityContext): S3Client {
13
+ return new S3Client({
14
+ region: ctx.region,
15
+ credentials: {
16
+ accessKeyId: ctx.credentials.accessKeyId,
17
+ secretAccessKey: ctx.credentials.secretAccessKey,
18
+ sessionToken: ctx.credentials.sessionToken,
19
+ },
20
+ });
21
+ }
22
+
23
+ let factory: (ctx: EntityContext) => S3Client = defaultFactory;
24
+
25
+ export function getS3Client(ctx: EntityContext): S3Client {
26
+ return factory(ctx);
27
+ }
28
+
29
+ /** Test hook: replace the S3 client factory. */
30
+ export function _setSourcesS3Factory(f: (ctx: EntityContext) => S3Client): void {
31
+ factory = f;
32
+ }
33
+
34
+ /** Test hook: restore the default S3 client factory. */
35
+ export function _resetSourcesS3Factory(): void {
36
+ factory = defaultFactory;
37
+ }
38
+
39
+ export async function streamToString(body: unknown): Promise<string> {
40
+ const stream = body as AsyncIterable<Uint8Array>;
41
+ const chunks: Buffer[] = [];
42
+ for await (const chunk of stream) {
43
+ chunks.push(Buffer.from(chunk));
44
+ }
45
+ return Buffer.concat(chunks).toString("utf-8");
46
+ }
@@ -0,0 +1,247 @@
1
+ /**
2
+ * Unit tests for sources/list.ts.
3
+ *
4
+ * Uses an in-memory S3Client stub installed via the internal `_setSourcesS3Factory`
5
+ * hook so we don't have to stand up vi.mock for @aws-sdk/client-s3.
6
+ */
7
+
8
+ import { describe, it, expect, beforeEach, afterEach } from "vitest";
9
+ import {
10
+ GetObjectCommand,
11
+ ListObjectsV2Command,
12
+ type S3Client,
13
+ } from "@aws-sdk/client-s3";
14
+ import { listSources } from "./list.js";
15
+ import {
16
+ _setSourcesS3Factory,
17
+ _resetSourcesS3Factory,
18
+ } from "./internals.js";
19
+ import type { EntityContext } from "../types.js";
20
+ import { InvalidSourceChannelError } from "../schemas/source-channels.js";
21
+
22
+ // ---------------------------------------------------------------------------
23
+ // Fixtures + helpers
24
+ // ---------------------------------------------------------------------------
25
+
26
+ const ENTITY: EntityContext = {
27
+ uid: "cmp_test_001",
28
+ slug: "test",
29
+ bucketName: "hq-vault-test",
30
+ region: "us-east-1",
31
+ credentials: {
32
+ accessKeyId: "ASIATEST",
33
+ secretAccessKey: "secret",
34
+ sessionToken: "session",
35
+ },
36
+ expiresAt: new Date(Date.now() + 3600_000).toISOString(),
37
+ };
38
+
39
+ const MEETING_MD = `---
40
+ source_id: abc
41
+ source_type: meeting
42
+ channel: meeting
43
+ title: Hello
44
+ ---
45
+
46
+ Body text.
47
+ `;
48
+
49
+ interface StoredObject {
50
+ key: string;
51
+ content: string;
52
+ lastModified: Date;
53
+ }
54
+
55
+ /** Build an in-memory S3 stub. Pagination is keyed by ContinuationToken (string offset). */
56
+ function buildStub(
57
+ objects: StoredObject[],
58
+ observed: { commands: unknown[] } = { commands: [] },
59
+ ): S3Client {
60
+ async function send(command: unknown): Promise<unknown> {
61
+ observed.commands.push(command);
62
+
63
+ if (command instanceof ListObjectsV2Command) {
64
+ const { Prefix, MaxKeys, ContinuationToken } = command.input as {
65
+ Prefix?: string;
66
+ MaxKeys?: number;
67
+ ContinuationToken?: string;
68
+ };
69
+ const filtered = objects.filter((o) => !Prefix || o.key.startsWith(Prefix));
70
+ const offset = ContinuationToken ? parseInt(ContinuationToken, 10) : 0;
71
+ const max = MaxKeys ?? 1000;
72
+ const page = filtered.slice(offset, offset + max);
73
+ const nextOffset = offset + page.length;
74
+ const truncated = nextOffset < filtered.length;
75
+ return {
76
+ Contents: page.map((o) => ({
77
+ Key: o.key,
78
+ Size: Buffer.byteLength(o.content, "utf-8"),
79
+ LastModified: o.lastModified,
80
+ ETag: '"mock"',
81
+ })),
82
+ IsTruncated: truncated,
83
+ NextContinuationToken: truncated ? String(nextOffset) : undefined,
84
+ $metadata: {},
85
+ };
86
+ }
87
+
88
+ if (command instanceof GetObjectCommand) {
89
+ const { Key } = command.input as { Key: string };
90
+ const found = objects.find((o) => o.key === Key);
91
+ if (!found) {
92
+ const err = new Error(`NoSuchKey: ${Key}`);
93
+ (err as Error & { name: string }).name = "NoSuchKey";
94
+ throw err;
95
+ }
96
+ const content = found.content;
97
+ async function* stream(): AsyncIterable<Uint8Array> {
98
+ yield Buffer.from(content, "utf-8");
99
+ }
100
+ return { Body: stream(), $metadata: {} };
101
+ }
102
+
103
+ throw new Error(`unhandled command: ${(command as { constructor?: { name?: string } }).constructor?.name}`);
104
+ }
105
+ return { send } as unknown as S3Client;
106
+ }
107
+
108
+ function installStub(objects: StoredObject[]): { commands: unknown[] } {
109
+ const observed = { commands: [] as unknown[] };
110
+ const stub = buildStub(objects, observed);
111
+ _setSourcesS3Factory(() => stub);
112
+ return observed;
113
+ }
114
+
115
+ beforeEach(() => {
116
+ _resetSourcesS3Factory();
117
+ });
118
+
119
+ afterEach(() => {
120
+ _resetSourcesS3Factory();
121
+ });
122
+
123
+ // ---------------------------------------------------------------------------
124
+ // Tests
125
+ // ---------------------------------------------------------------------------
126
+
127
+ describe("listSources", () => {
128
+ it("happy path: lists one source and skips its .raw.json sibling", async () => {
129
+ installStub([
130
+ {
131
+ key: "sources/meeting/abc.md",
132
+ content: MEETING_MD,
133
+ lastModified: new Date("2026-03-15T14:00:00Z"),
134
+ },
135
+ {
136
+ key: "sources/meeting/abc.raw.json",
137
+ content: '{"raw": true}',
138
+ lastModified: new Date("2026-03-15T14:00:00Z"),
139
+ },
140
+ ]);
141
+
142
+ const result = await listSources({ entity: ENTITY, channel: "meeting" });
143
+
144
+ expect(result.entries).toHaveLength(1);
145
+ expect(result.entries[0].sourceId).toBe("abc");
146
+ expect(result.entries[0].channel).toBe("meeting");
147
+ expect(result.entries[0].key).toBe("sources/meeting/abc.md");
148
+ expect(result.entries[0].size).toBeGreaterThan(0);
149
+ expect(result.entries[0].frontmatter).toBeUndefined();
150
+ expect(result.nextToken).toBeUndefined();
151
+ });
152
+
153
+ it("pagination: returns nextToken and accepts it on the next call", async () => {
154
+ const objects: StoredObject[] = Array.from({ length: 5 }, (_, i) => ({
155
+ key: `sources/meeting/m${i}.md`,
156
+ content: MEETING_MD,
157
+ lastModified: new Date("2026-03-15T14:00:00Z"),
158
+ }));
159
+ installStub(objects);
160
+
161
+ const page1 = await listSources({
162
+ entity: ENTITY,
163
+ channel: "meeting",
164
+ limit: 2,
165
+ });
166
+ expect(page1.entries).toHaveLength(2);
167
+ expect(page1.entries.map((e) => e.sourceId)).toEqual(["m0", "m1"]);
168
+ expect(page1.nextToken).toBe("2");
169
+
170
+ const page2 = await listSources({
171
+ entity: ENTITY,
172
+ channel: "meeting",
173
+ limit: 2,
174
+ continuationToken: page1.nextToken,
175
+ });
176
+ expect(page2.entries.map((e) => e.sourceId)).toEqual(["m2", "m3"]);
177
+ expect(page2.nextToken).toBe("4");
178
+
179
+ const page3 = await listSources({
180
+ entity: ENTITY,
181
+ channel: "meeting",
182
+ limit: 2,
183
+ continuationToken: page2.nextToken,
184
+ });
185
+ expect(page3.entries.map((e) => e.sourceId)).toEqual(["m4"]);
186
+ expect(page3.nextToken).toBeUndefined();
187
+ });
188
+
189
+ it("includeFrontmatter:true fetches each object and parses YAML", async () => {
190
+ const observed = installStub([
191
+ {
192
+ key: "sources/meeting/abc.md",
193
+ content: MEETING_MD,
194
+ lastModified: new Date("2026-03-15T14:00:00Z"),
195
+ },
196
+ ]);
197
+
198
+ const result = await listSources({
199
+ entity: ENTITY,
200
+ channel: "meeting",
201
+ includeFrontmatter: true,
202
+ });
203
+
204
+ expect(result.entries).toHaveLength(1);
205
+ expect(result.entries[0].frontmatter).toEqual({
206
+ source_id: "abc",
207
+ source_type: "meeting",
208
+ channel: "meeting",
209
+ title: "Hello",
210
+ });
211
+ // 1 list + 1 get for the single entry.
212
+ expect(observed.commands.filter((c) => c instanceof GetObjectCommand)).toHaveLength(1);
213
+ });
214
+
215
+ it("includeFrontmatter:false (default) does not perform GETs", async () => {
216
+ const observed = installStub([
217
+ {
218
+ key: "sources/meeting/abc.md",
219
+ content: MEETING_MD,
220
+ lastModified: new Date(),
221
+ },
222
+ ]);
223
+
224
+ await listSources({ entity: ENTITY, channel: "meeting" });
225
+
226
+ expect(observed.commands.filter((c) => c instanceof GetObjectCommand)).toHaveLength(0);
227
+ });
228
+
229
+ it("uses the correct prefix per channel", async () => {
230
+ const observed = installStub([]);
231
+ await listSources({ entity: ENTITY, channel: "slack" });
232
+ const listCmd = observed.commands[0] as ListObjectsV2Command;
233
+ expect((listCmd.input as { Prefix?: string }).Prefix).toBe("sources/slack/");
234
+ });
235
+
236
+ it("rejects an invalid channel via assertSourceChannel BEFORE any S3 call", async () => {
237
+ const observed = installStub([]);
238
+ await expect(
239
+ listSources({
240
+ entity: ENTITY,
241
+ // @ts-expect-error — invalid channel intentional for negative test
242
+ channel: "pigeon",
243
+ }),
244
+ ).rejects.toBeInstanceOf(InvalidSourceChannelError);
245
+ expect(observed.commands).toHaveLength(0);
246
+ });
247
+ });
@@ -0,0 +1,95 @@
1
+ /**
2
+ * listSources — page through `sources/{channel}/` keys in an entity bucket
3
+ * and return typed summaries.
4
+ *
5
+ * The `.raw.json` siblings written by the sources-pipeline are skipped from
6
+ * the summary (callers fetch them via getSource({ includeRaw: true })).
7
+ */
8
+
9
+ import {
10
+ GetObjectCommand,
11
+ ListObjectsV2Command,
12
+ } from "@aws-sdk/client-s3";
13
+ import { assertSourceChannel } from "../schemas/source-channels.js";
14
+ import { getS3Client, streamToString } from "./internals.js";
15
+ import { parseFrontmatter } from "./parse.js";
16
+ import type {
17
+ ListSourcesOptions,
18
+ ListSourcesResult,
19
+ SourceSummary,
20
+ } from "./types.js";
21
+
22
+ /**
23
+ * Strip the `sources/{channel}/` prefix and `.md` / `.raw.json` suffix from a key.
24
+ * Returns `null` if the key shape isn't recognised; the `isRawSibling` flag tells
25
+ * callers to skip pipeline-internal `.raw.json` companions from summaries.
26
+ */
27
+ function deriveSourceId(
28
+ key: string,
29
+ prefix: string,
30
+ ): { sourceId: string; isRawSibling: boolean } | null {
31
+ if (!key.startsWith(prefix)) return null;
32
+ const tail = key.slice(prefix.length);
33
+ if (tail.endsWith(".raw.json")) {
34
+ return { sourceId: tail.slice(0, -".raw.json".length), isRawSibling: true };
35
+ }
36
+ if (tail.endsWith(".md")) {
37
+ return { sourceId: tail.slice(0, -".md".length), isRawSibling: false };
38
+ }
39
+ return null;
40
+ }
41
+
42
+ export async function listSources(opts: ListSourcesOptions): Promise<ListSourcesResult> {
43
+ // Validate channel BEFORE any S3 call (acceptance criterion).
44
+ assertSourceChannel(opts.channel);
45
+
46
+ const client = getS3Client(opts.entity);
47
+ const prefix = `sources/${opts.channel}/`;
48
+
49
+ const response = await client.send(
50
+ new ListObjectsV2Command({
51
+ Bucket: opts.entity.bucketName,
52
+ Prefix: prefix,
53
+ MaxKeys: opts.limit,
54
+ ContinuationToken: opts.continuationToken,
55
+ }),
56
+ );
57
+
58
+ const entries: SourceSummary[] = [];
59
+ for (const obj of response.Contents ?? []) {
60
+ if (!obj.Key) continue;
61
+ const derived = deriveSourceId(obj.Key, prefix);
62
+ if (!derived || derived.isRawSibling) continue;
63
+
64
+ const summary: SourceSummary = {
65
+ sourceId: derived.sourceId,
66
+ channel: opts.channel,
67
+ key: obj.Key,
68
+ lastModified: obj.LastModified ?? new Date(0),
69
+ size: obj.Size ?? 0,
70
+ };
71
+
72
+ if (opts.includeFrontmatter) {
73
+ try {
74
+ const get = await client.send(
75
+ new GetObjectCommand({
76
+ Bucket: opts.entity.bucketName,
77
+ Key: obj.Key,
78
+ }),
79
+ );
80
+ const text = await streamToString(get.Body);
81
+ const fm = parseFrontmatter(text);
82
+ if (fm) summary.frontmatter = fm;
83
+ } catch {
84
+ // Frontmatter fetch is best-effort; leave undefined on failure.
85
+ }
86
+ }
87
+
88
+ entries.push(summary);
89
+ }
90
+
91
+ return {
92
+ entries,
93
+ nextToken: response.IsTruncated ? response.NextContinuationToken : undefined,
94
+ };
95
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * YAML frontmatter parsing for source markdown documents.
3
+ *
4
+ * Format: a leading `---\n` block terminated by `\n---\n`, followed by the body.
5
+ * Tolerates LF and CRLF. Returns `{ frontmatter: null, body }` when no
6
+ * frontmatter block is present.
7
+ */
8
+
9
+ import { load as yamlLoad } from "js-yaml";
10
+
11
+ export interface ParsedMarkdown {
12
+ frontmatter: Record<string, unknown> | null;
13
+ body: string;
14
+ }
15
+
16
+ const FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/;
17
+
18
+ export function parseMarkdown(content: string): ParsedMarkdown {
19
+ const match = FRONTMATTER_RE.exec(content);
20
+ if (!match) {
21
+ return { frontmatter: null, body: content };
22
+ }
23
+
24
+ const [, yamlBlock, body] = match;
25
+ let frontmatter: Record<string, unknown> | null;
26
+ try {
27
+ const loaded = yamlLoad(yamlBlock);
28
+ frontmatter = loaded && typeof loaded === "object" && !Array.isArray(loaded)
29
+ ? (loaded as Record<string, unknown>)
30
+ : null;
31
+ } catch {
32
+ frontmatter = null;
33
+ }
34
+ return { frontmatter, body };
35
+ }
36
+
37
+ /**
38
+ * Convenience: parse only the frontmatter (used by listSources includeFrontmatter
39
+ * where we don't need the body).
40
+ */
41
+ export function parseFrontmatter(content: string): Record<string, unknown> | null {
42
+ return parseMarkdown(content).frontmatter;
43
+ }
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Public types for the sources read surface.
3
+ *
4
+ * Mirrors the sources-pipeline write path so a meeting source written by
5
+ * hq-pro is readable through this layer without translation.
6
+ */
7
+
8
+ import type { SourceChannel } from "../schemas/source-channels.js";
9
+
10
+ /**
11
+ * Lightweight summary returned by listSources(). `frontmatter` is only
12
+ * populated when the caller passes `includeFrontmatter: true`.
13
+ */
14
+ export interface SourceSummary {
15
+ /** Stable ID derived from the object key (filename minus `.md`/`.raw.json`). */
16
+ sourceId: string;
17
+ channel: SourceChannel;
18
+ /** Full S3 object key (e.g. "sources/meeting/abc.md"). */
19
+ key: string;
20
+ lastModified: Date;
21
+ size: number;
22
+ /** Parsed YAML frontmatter — only present when listSources(includeFrontmatter:true). */
23
+ frontmatter?: Record<string, unknown>;
24
+ }
25
+
26
+ /**
27
+ * Full source document returned by getSource(). `frontmatter` is `null` if
28
+ * the document was malformed (no `---` delimiters) — body still contains the
29
+ * raw payload in that case so callers can fall back.
30
+ */
31
+ export interface SourceDocument {
32
+ key: string;
33
+ /** Parsed YAML frontmatter, or `null` if the document had no frontmatter block. */
34
+ frontmatter: Record<string, unknown> | null;
35
+ /** Markdown body (everything after the closing `---`). */
36
+ body: string;
37
+ /** Sibling `.raw.json` payload, only present when getSource(includeRaw:true). */
38
+ raw?: unknown;
39
+ }
40
+
41
+ /**
42
+ * Listing options for listSources().
43
+ */
44
+ export interface ListSourcesOptions {
45
+ entity: import("../types.js").EntityContext;
46
+ channel: SourceChannel;
47
+ /** Max keys per page; defaults to S3 default (1000). */
48
+ limit?: number;
49
+ /** Opaque continuation token returned by a prior page. */
50
+ continuationToken?: string;
51
+ /** When true, fetches+parses each entry's frontmatter (extra GETs). */
52
+ includeFrontmatter?: boolean;
53
+ }
54
+
55
+ export interface ListSourcesResult {
56
+ entries: SourceSummary[];
57
+ /** Present when the result was truncated; pass back as continuationToken. */
58
+ nextToken?: string;
59
+ }
60
+
61
+ export interface GetSourceOptions {
62
+ entity: import("../types.js").EntityContext;
63
+ channel: SourceChannel;
64
+ sourceId: string;
65
+ /** When true, also fetches the `.raw.json` sibling. */
66
+ includeRaw?: boolean;
67
+ }