@cogitator-ai/channels 0.9.3 → 0.10.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 (102) hide show
  1. package/README.md +101 -11
  2. package/dist/assistant-config.d.ts +16 -0
  3. package/dist/assistant-config.d.ts.map +1 -1
  4. package/dist/assistant-config.js +22 -0
  5. package/dist/assistant-config.js.map +1 -1
  6. package/dist/channels/bluesky/account.d.ts +18 -0
  7. package/dist/channels/bluesky/account.d.ts.map +1 -0
  8. package/dist/channels/bluesky/account.js +25 -0
  9. package/dist/channels/bluesky/account.js.map +1 -0
  10. package/dist/channels/bluesky/channel.d.ts +89 -0
  11. package/dist/channels/bluesky/channel.d.ts.map +1 -0
  12. package/dist/channels/bluesky/channel.js +427 -0
  13. package/dist/channels/bluesky/channel.js.map +1 -0
  14. package/dist/channels/bluesky/config.d.ts +16 -0
  15. package/dist/channels/bluesky/config.d.ts.map +1 -0
  16. package/dist/channels/bluesky/config.js +2 -0
  17. package/dist/channels/bluesky/config.js.map +1 -0
  18. package/dist/channels/bluesky/connection.d.ts +40 -0
  19. package/dist/channels/bluesky/connection.d.ts.map +1 -0
  20. package/dist/channels/bluesky/connection.js +159 -0
  21. package/dist/channels/bluesky/connection.js.map +1 -0
  22. package/dist/channels/bluesky/feed.d.ts +59 -0
  23. package/dist/channels/bluesky/feed.d.ts.map +1 -0
  24. package/dist/channels/bluesky/feed.js +324 -0
  25. package/dist/channels/bluesky/feed.js.map +1 -0
  26. package/dist/channels/bluesky/index.d.ts +5 -0
  27. package/dist/channels/bluesky/index.d.ts.map +1 -0
  28. package/dist/channels/bluesky/index.js +4 -0
  29. package/dist/channels/bluesky/index.js.map +1 -0
  30. package/dist/channels/threads/account.d.ts +42 -0
  31. package/dist/channels/threads/account.d.ts.map +1 -0
  32. package/dist/channels/threads/account.js +64 -0
  33. package/dist/channels/threads/account.js.map +1 -0
  34. package/dist/channels/threads/channel.d.ts +119 -0
  35. package/dist/channels/threads/channel.d.ts.map +1 -0
  36. package/dist/channels/threads/channel.js +405 -0
  37. package/dist/channels/threads/channel.js.map +1 -0
  38. package/dist/channels/threads/client.d.ts +42 -0
  39. package/dist/channels/threads/client.d.ts.map +1 -0
  40. package/dist/channels/threads/client.js +97 -0
  41. package/dist/channels/threads/client.js.map +1 -0
  42. package/dist/channels/threads/feed.d.ts +74 -0
  43. package/dist/channels/threads/feed.d.ts.map +1 -0
  44. package/dist/channels/threads/feed.js +372 -0
  45. package/dist/channels/threads/feed.js.map +1 -0
  46. package/dist/channels/threads/index.d.ts +6 -0
  47. package/dist/channels/threads/index.d.ts.map +1 -0
  48. package/dist/channels/threads/index.js +6 -0
  49. package/dist/channels/threads/index.js.map +1 -0
  50. package/dist/channels/threads/token.d.ts +51 -0
  51. package/dist/channels/threads/token.d.ts.map +1 -0
  52. package/dist/channels/threads/token.js +128 -0
  53. package/dist/channels/threads/token.js.map +1 -0
  54. package/dist/feeds/attachments.d.ts +15 -0
  55. package/dist/feeds/attachments.d.ts.map +1 -0
  56. package/dist/feeds/attachments.js +85 -0
  57. package/dist/feeds/attachments.js.map +1 -0
  58. package/dist/feeds/errors.d.ts +39 -0
  59. package/dist/feeds/errors.d.ts.map +1 -0
  60. package/dist/feeds/errors.js +62 -0
  61. package/dist/feeds/errors.js.map +1 -0
  62. package/dist/feeds/index.d.ts +8 -0
  63. package/dist/feeds/index.d.ts.map +1 -0
  64. package/dist/feeds/index.js +6 -0
  65. package/dist/feeds/index.js.map +1 -0
  66. package/dist/feeds/publish-store.d.ts +148 -0
  67. package/dist/feeds/publish-store.d.ts.map +1 -0
  68. package/dist/feeds/publish-store.js +345 -0
  69. package/dist/feeds/publish-store.js.map +1 -0
  70. package/dist/feeds/publisher.d.ts +118 -0
  71. package/dist/feeds/publisher.d.ts.map +1 -0
  72. package/dist/feeds/publisher.js +308 -0
  73. package/dist/feeds/publisher.js.map +1 -0
  74. package/dist/feeds/storage.d.ts +33 -0
  75. package/dist/feeds/storage.d.ts.map +1 -0
  76. package/dist/feeds/storage.js +61 -0
  77. package/dist/feeds/storage.js.map +1 -0
  78. package/dist/feeds/text.d.ts +26 -0
  79. package/dist/feeds/text.d.ts.map +1 -0
  80. package/dist/feeds/text.js +126 -0
  81. package/dist/feeds/text.js.map +1 -0
  82. package/dist/feeds/token-store.d.ts +45 -0
  83. package/dist/feeds/token-store.d.ts.map +1 -0
  84. package/dist/feeds/token-store.js +128 -0
  85. package/dist/feeds/token-store.js.map +1 -0
  86. package/dist/formatters/markdown.d.ts.map +1 -1
  87. package/dist/formatters/markdown.js +6 -0
  88. package/dist/formatters/markdown.js.map +1 -1
  89. package/dist/formatters/plain-text.d.ts +7 -0
  90. package/dist/formatters/plain-text.d.ts.map +1 -0
  91. package/dist/formatters/plain-text.js +38 -0
  92. package/dist/formatters/plain-text.js.map +1 -0
  93. package/dist/gateway.js +1 -1
  94. package/dist/gateway.js.map +1 -1
  95. package/dist/index.d.ts +5 -1
  96. package/dist/index.d.ts.map +1 -1
  97. package/dist/index.js +4 -0
  98. package/dist/index.js.map +1 -1
  99. package/dist/runtime-builder.d.ts.map +1 -1
  100. package/dist/runtime-builder.js +54 -0
  101. package/dist/runtime-builder.js.map +1 -1
  102. package/package.json +23 -15
@@ -0,0 +1,308 @@
1
+ import { nanoid } from 'nanoid';
2
+ import { FeedError } from './errors.js';
3
+ import { MemoryPublishStore, } from './publish-store.js';
4
+ import { fitText } from './text.js';
5
+ const DEFAULT_RETRY = {
6
+ maxAttempts: 5,
7
+ baseDelay: 60_000,
8
+ maxDelay: 60 * 60_000,
9
+ };
10
+ /**
11
+ * Publishes posts to several feeds: now or at a time, each feed tried and
12
+ * retried on its own, each outcome saved as soon as it is known. Each
13
+ * delivery keeps the feed's idempotency key from before its first attempt,
14
+ * so an attempt after a crash or a lost answer finds the post instead of
15
+ * publishing it twice, and a key makes the whole publication idempotent.
16
+ */
17
+ export class FeedPublisher {
18
+ options;
19
+ feeds = new Map();
20
+ store;
21
+ retry;
22
+ pollInterval;
23
+ claimTtl;
24
+ now;
25
+ owner = `feedpub_${nanoid(12)}`;
26
+ timer;
27
+ ticking;
28
+ stopping = false;
29
+ connected = new Set();
30
+ constructor(options) {
31
+ this.options = options;
32
+ if (options.feeds.length === 0)
33
+ throw new Error('FeedPublisher needs at least one feed');
34
+ for (const feed of options.feeds) {
35
+ if (this.feeds.has(feed.type)) {
36
+ throw new Error(`FeedPublisher got two feeds of type "${feed.type}"`);
37
+ }
38
+ this.feeds.set(feed.type, feed);
39
+ }
40
+ this.now = options.now ?? Date.now;
41
+ this.store = options.store ?? new MemoryPublishStore(this.now);
42
+ this.retry = { ...DEFAULT_RETRY, ...options.retry };
43
+ this.pollInterval = options.pollInterval ?? 15_000;
44
+ this.claimTtl = options.claimTtl ?? 5 * 60_000;
45
+ }
46
+ /** Publishes `post` now, or schedules it with `publishAt`. */
47
+ async publish(post, options = {}) {
48
+ const feeds = options.feeds ?? [...this.feeds.keys()];
49
+ if (feeds.length === 0)
50
+ throw new Error('No feed to publish to');
51
+ for (const feed of feeds) {
52
+ if (!this.feeds.has(feed)) {
53
+ throw new Error(`Unknown feed "${feed}": the publisher has ${[...this.feeds.keys()].join(', ')}`);
54
+ }
55
+ }
56
+ const now = this.now();
57
+ const publishAt = options.publishAt === undefined
58
+ ? now
59
+ : typeof options.publishAt === 'number'
60
+ ? options.publishAt
61
+ : options.publishAt.getTime();
62
+ if (!Number.isFinite(publishAt)) {
63
+ throw new Error(`publishAt must be a valid time, got ${String(options.publishAt)}`);
64
+ }
65
+ const key = options.key && (this.options.dryRun ? `dry-run:${options.key}` : options.key);
66
+ const job = {
67
+ id: `feedjob_${nanoid(12)}`,
68
+ ...(key ? { key } : {}),
69
+ post,
70
+ createdAt: now,
71
+ deliveries: [...new Set(feeds)].map((feed) => ({
72
+ feed,
73
+ status: 'pending',
74
+ attempts: 0,
75
+ nextAttemptAt: publishAt,
76
+ })),
77
+ };
78
+ const stored = await this.store.add(job);
79
+ const duplicate = stored.id !== job.id;
80
+ if (publishAt > now)
81
+ this.connect(feeds);
82
+ if (duplicate || publishAt > now || !(await this.store.claim(stored.id, this.claimOf()))) {
83
+ return { job: stored, pending: stored.deliveries.some(isWaiting), duplicate };
84
+ }
85
+ const done = await this.run(stored);
86
+ return { job: done, pending: done.deliveries.some(isWaiting), duplicate };
87
+ }
88
+ /** Looks for due jobs every `pollInterval` until `stop()`: scheduled posts and retries. */
89
+ start() {
90
+ if (this.timer)
91
+ return;
92
+ this.connect([...this.feeds.keys()]);
93
+ this.timer = setInterval(() => void this.tick(), this.pollInterval);
94
+ void this.tick();
95
+ }
96
+ /** Stops looking for due jobs, letting the job being published finish, and waits for it. */
97
+ async stop() {
98
+ this.stopping = true;
99
+ if (this.timer)
100
+ clearInterval(this.timer);
101
+ this.timer = undefined;
102
+ await this.ticking;
103
+ }
104
+ /**
105
+ * Publishes the jobs that are due now, one at a time, so a claim is taken
106
+ * only when the job is about to be published. `start()` does this on a timer.
107
+ */
108
+ async tick() {
109
+ if (this.ticking)
110
+ return this.ticking;
111
+ this.stopping = false;
112
+ this.ticking = (async () => {
113
+ const seen = new Set();
114
+ try {
115
+ while (!this.stopping) {
116
+ const job = await this.store.claimNext(this.now(), this.claimOf());
117
+ if (!job)
118
+ break;
119
+ if (seen.has(job.id)) {
120
+ await this.store.release(job.id, this.owner);
121
+ break;
122
+ }
123
+ seen.add(job.id);
124
+ await this.run(job);
125
+ }
126
+ }
127
+ catch (error) {
128
+ console.error('[feeds] Publishing due jobs failed:', error);
129
+ }
130
+ })().finally(() => {
131
+ this.ticking = undefined;
132
+ });
133
+ return this.ticking;
134
+ }
135
+ async get(id) {
136
+ return this.store.get(id);
137
+ }
138
+ async list(filter) {
139
+ return this.store.list(filter);
140
+ }
141
+ /** Drops a job before it is published, `false` when it is missing or being published. */
142
+ async cancel(id) {
143
+ return this.store.remove(id);
144
+ }
145
+ /**
146
+ * Connects feeds that will publish later, so a feed that keeps a token
147
+ * renews it until then instead of finding it expired when the post is due.
148
+ * A feed that cannot connect now is tried again the next time.
149
+ */
150
+ connect(types) {
151
+ if (this.options.dryRun)
152
+ return;
153
+ for (const type of types) {
154
+ const feed = this.feeds.get(type);
155
+ if (!feed || this.connected.has(feed))
156
+ continue;
157
+ this.connected.add(feed);
158
+ feed.connect().catch((error) => {
159
+ this.connected.delete(feed);
160
+ console.error(`[feeds] Could not connect the ${type} feed:`, error);
161
+ });
162
+ }
163
+ }
164
+ claimOf() {
165
+ return { owner: this.owner, ttl: this.claimTtl };
166
+ }
167
+ /** Publishes a job this publisher claimed, extending the claim while it works, then releases it. */
168
+ async run(job) {
169
+ const lost = new AbortController();
170
+ const heartbeat = setInterval(() => {
171
+ this.store.extend(job.id, this.claimOf()).then((held) => {
172
+ if (!held)
173
+ lost.abort(new Error(`Lost the claim on feed job ${job.id}`));
174
+ }, (error) => {
175
+ console.error(`[feeds] Could not extend the claim on feed job ${job.id}:`, error);
176
+ lost.abort(new Error(`Could not extend the claim on feed job ${job.id}`));
177
+ });
178
+ }, Math.max(1, Math.floor(this.claimTtl / 3)));
179
+ heartbeat.unref?.();
180
+ try {
181
+ return await this.process(job, lost.signal);
182
+ }
183
+ finally {
184
+ clearInterval(heartbeat);
185
+ await this.store.release(job.id, this.owner);
186
+ }
187
+ }
188
+ async process(job, signal) {
189
+ for (const delivery of job.deliveries) {
190
+ if (delivery.status !== 'pending' || delivery.nextAttemptAt > this.now())
191
+ continue;
192
+ const feed = this.feeds.get(delivery.feed);
193
+ const retry = delivery.idempotencyKey !== undefined;
194
+ if (feed && !this.options.dryRun && !retry) {
195
+ delivery.idempotencyKey = feed.idempotencyKey();
196
+ if (!(await this.save(job, signal)))
197
+ break;
198
+ }
199
+ await this.attempt(job, delivery, feed, retry, signal);
200
+ if (!(await this.save(job, signal)))
201
+ break;
202
+ }
203
+ return job;
204
+ }
205
+ /** Saves the job while the claim holds, `false` when another worker took it over. */
206
+ async save(job, signal) {
207
+ if (!signal.aborted && (await this.store.save(job, this.owner)))
208
+ return true;
209
+ console.warn(`[feeds] Lost the claim on feed job ${job.id}: another worker took it over`);
210
+ return false;
211
+ }
212
+ async attempt(job, delivery, feed, retry, signal) {
213
+ delivery.attempts += 1;
214
+ try {
215
+ if (!feed)
216
+ throw new FeedError(delivery.feed, 'unknown', 'The publisher has no such feed');
217
+ const published = await this.send(feed, job.post, delivery.idempotencyKey, retry, signal);
218
+ delivery.status = 'published';
219
+ delivery.post = {
220
+ id: published.id,
221
+ url: published.url,
222
+ publishedAt: published.publishedAt.getTime(),
223
+ };
224
+ delete delivery.error;
225
+ delete delivery.idempotencyKey;
226
+ await this.notify(() => this.options.onPublished?.({ job, feed: delivery.feed, post: published }));
227
+ }
228
+ catch (caught) {
229
+ if (signal.aborted)
230
+ return;
231
+ const error = caught instanceof Error ? caught : new Error(String(caught));
232
+ const quota = error instanceof FeedError && error.code === 'quota_exceeded';
233
+ if (quota)
234
+ delivery.attempts -= 1;
235
+ const again = error instanceof FeedError &&
236
+ error.retryable &&
237
+ (quota || delivery.attempts < this.retry.maxAttempts);
238
+ delivery.error = {
239
+ code: error instanceof FeedError ? error.code : 'unknown',
240
+ message: error.message,
241
+ };
242
+ if (again) {
243
+ const wait = quota
244
+ ? (error.retryAfter ?? this.retry.maxDelay)
245
+ : Math.max(error.retryAfter ?? 0, this.backoff(delivery.attempts));
246
+ delivery.nextAttemptAt = this.now() + wait;
247
+ }
248
+ else {
249
+ delivery.status = 'failed';
250
+ }
251
+ await this.notify(() => this.options.onFailed?.({ job, feed: delivery.feed, error, final: !again }));
252
+ }
253
+ }
254
+ async send(feed, post, idempotencyKey, retry, signal) {
255
+ const measure = lengthOf(feed);
256
+ const { maxLength } = feed.limits;
257
+ let text = post.text;
258
+ if (measure(text) > maxLength) {
259
+ if (this.options.overflow === 'fail') {
260
+ throw new FeedError(feed.type, 'invalid_post', `The text is longer than ${feed.type} takes (${describeLimits(feed)})`);
261
+ }
262
+ text = fitText(text, maxLength, measure);
263
+ }
264
+ if (this.options.dryRun) {
265
+ return {
266
+ feed: feed.type,
267
+ id: `dry-run:${nanoid(8)}`,
268
+ url: '',
269
+ publishedAt: new Date(this.now()),
270
+ };
271
+ }
272
+ return feed.publish(text === post.text ? post : { ...post, text }, {
273
+ signal,
274
+ ...(idempotencyKey && { idempotencyKey, retry }),
275
+ });
276
+ }
277
+ backoff(attempts) {
278
+ return Math.min(this.retry.baseDelay * 2 ** (attempts - 1), this.retry.maxDelay);
279
+ }
280
+ async notify(hook) {
281
+ try {
282
+ await hook();
283
+ }
284
+ catch (error) {
285
+ console.error('[feeds] A publisher hook failed:', error);
286
+ }
287
+ }
288
+ }
289
+ function isWaiting(delivery) {
290
+ return delivery.status === 'pending';
291
+ }
292
+ const encoder = new TextEncoder();
293
+ /**
294
+ * The length of a text against `limits.maxLength`, folding in a limit in
295
+ * bytes: a text fits when this is at most `maxLength` exactly when it fits
296
+ * both, so one cut honors both limits.
297
+ */
298
+ function lengthOf(feed) {
299
+ const { maxLength, maxBytes } = feed.limits;
300
+ if (maxBytes === undefined)
301
+ return (text) => feed.measure(text);
302
+ return (text) => Math.max(feed.measure(text), Math.ceil((encoder.encode(text).length * maxLength) / maxBytes));
303
+ }
304
+ function describeLimits(feed) {
305
+ const { maxLength, maxBytes } = feed.limits;
306
+ return maxBytes === undefined ? `${maxLength}` : `${maxLength} and ${maxBytes} bytes`;
307
+ }
308
+ //# sourceMappingURL=publisher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"publisher.js","sourceRoot":"","sources":["../../src/feeds/publisher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,33 @@
1
+ /** The part of a `pg` Pool or Client the Postgres stores use. */
2
+ export interface PgClient {
3
+ query(text: string, values?: unknown[]): Promise<{
4
+ rows: Array<Record<string, unknown>>;
5
+ }>;
6
+ }
7
+ /** A table name a store accepts: an identifier, optionally schema-qualified. */
8
+ export declare const TABLE_NAME: RegExp;
9
+ /**
10
+ * Runs a `CREATE ... IF NOT EXISTS` statement. Two sessions creating the
11
+ * same object at once can still collide on Postgres' catalog. By the retry
12
+ * the object exists and the statement succeeds.
13
+ */
14
+ export declare function createIfMissing(client: PgClient, statement: string): Promise<void>;
15
+ /** A JSONB column: `pg` returns it parsed, other drivers may return the text. */
16
+ export declare function fromJson(data: unknown): unknown;
17
+ /**
18
+ * Writes `content` to `path` through a temporary file and a rename, so a
19
+ * crash never leaves half a file. The file is readable by its owner only,
20
+ * since stores keep secrets in it.
21
+ */
22
+ export declare function writeFileAtomic(path: string, content: string): Promise<void>;
23
+ /** Runs `task` after every task queued before it, so writes to one file never interleave. */
24
+ export declare class SerialQueue {
25
+ private tail;
26
+ run<T>(task: () => Promise<T>): Promise<T>;
27
+ }
28
+ /**
29
+ * The queue of one file, shared by every store of this process that uses
30
+ * it, so two stores on the same file never lose each other's writes.
31
+ */
32
+ export declare function queueFor(path: string): SerialQueue;
33
+ //# sourceMappingURL=storage.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../../src/feeds/storage.ts"],"names":[],"mappings":"AAGA,iEAAiE;AACjE,MAAM,WAAW,QAAQ;IACvB,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;QAAE,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;KAAE,CAAC,CAAC;CAC5F;AAED,gFAAgF;AAChF,eAAO,MAAM,UAAU,QAAwD,CAAC;AAIhF;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,MAAM,EAAE,QAAQ,EAAE,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CASxF;AAED,iFAAiF;AACjF,wBAAgB,QAAQ,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,CAE/C;AAID;;;;GAIG;AACH,wBAAsB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAKlF;AAED,6FAA6F;AAC7F,qBAAa,WAAW;IACtB,OAAO,CAAC,IAAI,CAAuC;IAEnD,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;CAK3C;AAID;;;GAGG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAQlD"}
@@ -0,0 +1,61 @@
1
+ import { mkdir, rename, writeFile } from 'node:fs/promises';
2
+ import { dirname, resolve } from 'node:path';
3
+ /** A table name a store accepts: an identifier, optionally schema-qualified. */
4
+ export const TABLE_NAME = /^[A-Za-z_][A-Za-z0-9_]*(\.[A-Za-z_][A-Za-z0-9_]*)?$/;
5
+ const DUPLICATE_OBJECT_CODES = new Set(['23505', '42P07', '42710']);
6
+ /**
7
+ * Runs a `CREATE ... IF NOT EXISTS` statement. Two sessions creating the
8
+ * same object at once can still collide on Postgres' catalog. By the retry
9
+ * the object exists and the statement succeeds.
10
+ */
11
+ export async function createIfMissing(client, statement) {
12
+ try {
13
+ await client.query(statement);
14
+ }
15
+ catch (error) {
16
+ const code = typeof error === 'object' && error !== null && 'code' in error ? error.code : undefined;
17
+ if (typeof code !== 'string' || !DUPLICATE_OBJECT_CODES.has(code))
18
+ throw error;
19
+ await client.query(statement);
20
+ }
21
+ }
22
+ /** A JSONB column: `pg` returns it parsed, other drivers may return the text. */
23
+ export function fromJson(data) {
24
+ return typeof data === 'string' ? JSON.parse(data) : data;
25
+ }
26
+ let temporaryCounter = 0;
27
+ /**
28
+ * Writes `content` to `path` through a temporary file and a rename, so a
29
+ * crash never leaves half a file. The file is readable by its owner only,
30
+ * since stores keep secrets in it.
31
+ */
32
+ export async function writeFileAtomic(path, content) {
33
+ await mkdir(dirname(path), { recursive: true });
34
+ const temporary = `${path}.${process.pid}.${++temporaryCounter}.tmp`;
35
+ await writeFile(temporary, content, { mode: 0o600 });
36
+ await rename(temporary, path);
37
+ }
38
+ /** Runs `task` after every task queued before it, so writes to one file never interleave. */
39
+ export class SerialQueue {
40
+ tail = Promise.resolve();
41
+ run(task) {
42
+ const result = this.tail.then(task, task);
43
+ this.tail = result.catch(() => undefined);
44
+ return result;
45
+ }
46
+ }
47
+ const fileQueues = new Map();
48
+ /**
49
+ * The queue of one file, shared by every store of this process that uses
50
+ * it, so two stores on the same file never lose each other's writes.
51
+ */
52
+ export function queueFor(path) {
53
+ const file = resolve(path);
54
+ let queue = fileQueues.get(file);
55
+ if (!queue) {
56
+ queue = new SerialQueue();
57
+ fileQueues.set(file, queue);
58
+ }
59
+ return queue;
60
+ }
61
+ //# sourceMappingURL=storage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"storage.js","sourceRoot":"","sources":["../../src/feeds/storage.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAC;AAC5D,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAO7C,gFAAgF;AAChF,MAAM,CAAC,MAAM,UAAU,GAAG,qDAAqD,CAAC;AAEhF,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,CAAC,CAAC,CAAC;AAEpE;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,MAAgB,EAAE,SAAiB;IACvE,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAChC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,IAAI,GACR,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,MAAM,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC;QAC1F,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,CAAC,sBAAsB,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,MAAM,KAAK,CAAC;QAC/E,MAAM,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC;IAChC,CAAC;AACH,CAAC;AAED,iFAAiF;AACjF,MAAM,UAAU,QAAQ,CAAC,IAAa;IACpC,OAAO,OAAO,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;AAC5D,CAAC;AAED,IAAI,gBAAgB,GAAG,CAAC,CAAC;AAEzB;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,IAAY,EAAE,OAAe;IACjE,MAAM,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAChD,MAAM,SAAS,GAAG,GAAG,IAAI,IAAI,OAAO,CAAC,GAAG,IAAI,EAAE,gBAAgB,MAAM,CAAC;IACrE,MAAM,SAAS,CAAC,SAAS,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACrD,MAAM,MAAM,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;AAChC,CAAC;AAED,6FAA6F;AAC7F,MAAM,OAAO,WAAW;IACd,IAAI,GAAqB,OAAO,CAAC,OAAO,EAAE,CAAC;IAEnD,GAAG,CAAI,IAAsB;QAC3B,MAAM,MAAM,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;QAC1C,IAAI,CAAC,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,SAAS,CAAC,CAAC;QAC1C,OAAO,MAAM,CAAC;IAChB,CAAC;CACF;AAED,MAAM,UAAU,GAAG,IAAI,GAAG,EAAuB,CAAC;AAElD;;;GAGG;AACH,MAAM,UAAU,QAAQ,CAAC,IAAY;IACnC,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC3B,IAAI,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACjC,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,KAAK,GAAG,IAAI,WAAW,EAAE,CAAC;QAC1B,UAAU,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC9B,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Text as social feeds count it: Bluesky in graphemes, Threads in
3
+ * characters with emoji counted by their UTF-8 bytes. A plain `.length`
4
+ * counts UTF-16 units, so it splits emoji and miscounts both.
5
+ */
6
+ /** The length of a text as a feed counts it. */
7
+ export type TextMeasure = (text: string) => number;
8
+ /** The graphemes of `text`: what a reader sees as one character each. */
9
+ export declare function graphemes(text: string): string[];
10
+ /** The length in graphemes, as Bluesky counts it. */
11
+ export declare const graphemeLength: TextMeasure;
12
+ /** The length with emoji counted by their UTF-8 bytes, as Threads counts it. */
13
+ export declare const threadsLength: TextMeasure;
14
+ /**
15
+ * `text` cut to `max` with `ellipsis` when it is longer: at the last word
16
+ * boundary in the second half of what fits, else mid-word, never inside a
17
+ * grapheme.
18
+ */
19
+ export declare function fitText(text: string, max: number, measure?: TextMeasure, ellipsis?: string): string;
20
+ /**
21
+ * `text` in parts of at most `max`, for a thread of posts or a run of
22
+ * messages: whole paragraphs where they fit, else lines, else words, else
23
+ * graphemes.
24
+ */
25
+ export declare function splitText(text: string, max: number, measure?: TextMeasure): string[];
26
+ //# sourceMappingURL=text.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.d.ts","sourceRoot":"","sources":["../../src/feeds/text.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,gDAAgD;AAChD,MAAM,MAAM,WAAW,GAAG,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAC;AAMnD,yEAAyE;AACzE,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,EAAE,CAEhD;AAED,qDAAqD;AACrD,eAAO,MAAM,cAAc,EAAE,WAA8C,CAAC;AAE5E,gFAAgF;AAChF,eAAO,MAAM,aAAa,EAAE,WAM3B,CAAC;AAcF;;;;GAIG;AACH,wBAAgB,OAAO,CACrB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,WAA4B,EACrC,QAAQ,SAAM,GACb,MAAM,CAsBR;AAwBD;;;;GAIG;AACH,wBAAgB,SAAS,CACvB,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,EACX,OAAO,GAAE,WAA4B,GACpC,MAAM,EAAE,CA8BV"}
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Text as social feeds count it: Bluesky in graphemes, Threads in
3
+ * characters with emoji counted by their UTF-8 bytes. A plain `.length`
4
+ * counts UTF-16 units, so it splits emoji and miscounts both.
5
+ */
6
+ const segmenter = new Intl.Segmenter(undefined, { granularity: 'grapheme' });
7
+ const encoder = new TextEncoder();
8
+ const EMOJI = /\p{Extended_Pictographic}|\p{Regional_Indicator}|⃣/u;
9
+ /** The graphemes of `text`: what a reader sees as one character each. */
10
+ export function graphemes(text) {
11
+ return Array.from(segmenter.segment(text), (segment) => segment.segment);
12
+ }
13
+ /** The length in graphemes, as Bluesky counts it. */
14
+ export const graphemeLength = (text) => graphemes(text).length;
15
+ /** The length with emoji counted by their UTF-8 bytes, as Threads counts it. */
16
+ export const threadsLength = (text) => {
17
+ let total = 0;
18
+ for (const grapheme of graphemes(text)) {
19
+ total += EMOJI.test(grapheme) ? encoder.encode(grapheme).length : 1;
20
+ }
21
+ return total;
22
+ };
23
+ /** How many of the first `parts` fit in `max` together. */
24
+ function fittingCount(parts, max, measure) {
25
+ let low = 0;
26
+ let high = parts.length;
27
+ while (low < high) {
28
+ const middle = Math.ceil((low + high) / 2);
29
+ if (measure(parts.slice(0, middle).join('')) <= max)
30
+ low = middle;
31
+ else
32
+ high = middle - 1;
33
+ }
34
+ return low;
35
+ }
36
+ /**
37
+ * `text` cut to `max` with `ellipsis` when it is longer: at the last word
38
+ * boundary in the second half of what fits, else mid-word, never inside a
39
+ * grapheme.
40
+ */
41
+ export function fitText(text, max, measure = graphemeLength, ellipsis = '…') {
42
+ if (measure(text) <= max)
43
+ return text;
44
+ const room = max - measure(ellipsis);
45
+ if (room <= 0) {
46
+ const marks = graphemes(ellipsis);
47
+ return marks.slice(0, fittingCount(marks, max, measure)).join('');
48
+ }
49
+ const parts = graphemes(text);
50
+ const count = fittingCount(parts, room, measure);
51
+ let cut = count;
52
+ for (let i = count; i > count / 2; i--) {
53
+ if (/\s/u.test(parts[i] ?? '')) {
54
+ cut = i;
55
+ break;
56
+ }
57
+ }
58
+ return (parts
59
+ .slice(0, cut)
60
+ .join('')
61
+ .replace(/[\s,;:\u2013\u2014-]+$/u, '') + ellipsis);
62
+ }
63
+ /** Splits a line longer than `max` at word boundaries, words longer than `max` mid-word. */
64
+ function splitWords(line, max, measure) {
65
+ const out = [];
66
+ let current = '';
67
+ for (const word of line.split(/(?<=\s)/u)) {
68
+ if (measure((current + word).trimEnd()) <= max) {
69
+ current += word;
70
+ continue;
71
+ }
72
+ if (current.trim())
73
+ out.push(current.trimEnd());
74
+ let rest = graphemes(word);
75
+ while (measure(rest.join('').trimEnd()) > max) {
76
+ const count = Math.max(fittingCount(rest, max, measure), 1);
77
+ out.push(rest.slice(0, count).join(''));
78
+ rest = rest.slice(count);
79
+ }
80
+ current = rest.join('');
81
+ }
82
+ if (current.trim())
83
+ out.push(current.trimEnd());
84
+ return out;
85
+ }
86
+ /**
87
+ * `text` in parts of at most `max`, for a thread of posts or a run of
88
+ * messages: whole paragraphs where they fit, else lines, else words, else
89
+ * graphemes.
90
+ */
91
+ export function splitText(text, max, measure = graphemeLength) {
92
+ if (max < 1)
93
+ throw new RangeError(`max must be at least 1, got ${max}`);
94
+ const trimmed = text.trim();
95
+ if (!trimmed)
96
+ return [];
97
+ if (measure(trimmed) <= max)
98
+ return [trimmed];
99
+ const out = [];
100
+ let current = '';
101
+ const flush = () => {
102
+ if (current.trim())
103
+ out.push(current.trim());
104
+ current = '';
105
+ };
106
+ for (const block of trimmed.split(/\n{2,}/u)) {
107
+ const pieces = measure(block) <= max
108
+ ? [block]
109
+ : block
110
+ .split('\n')
111
+ .flatMap((line) => (measure(line) <= max ? [line] : splitWords(line, max, measure)));
112
+ for (const [index, piece] of pieces.entries()) {
113
+ const separator = current ? (index > 0 ? '\n' : '\n\n') : '';
114
+ if (current && measure(current + separator + piece) <= max) {
115
+ current += separator + piece;
116
+ }
117
+ else {
118
+ flush();
119
+ current = piece;
120
+ }
121
+ }
122
+ }
123
+ flush();
124
+ return out;
125
+ }
126
+ //# sourceMappingURL=text.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"text.js","sourceRoot":"","sources":["../../src/feeds/text.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ import type { StoredToken, TokenStore } from '@cogitator-ai/types';
2
+ import { type PgClient } from './storage.js';
3
+ /** Tokens in memory: they are gone when the process ends. For tests and one-off scripts. */
4
+ export declare class MemoryTokenStore implements TokenStore {
5
+ private readonly tokens;
6
+ get(key: string): Promise<StoredToken | undefined>;
7
+ set(key: string, token: StoredToken): Promise<void>;
8
+ delete(key: string): Promise<void>;
9
+ }
10
+ export interface FileTokenStoreOptions {
11
+ /** The JSON file, readable by its owner only (default `~/.cogitator/tokens.json`). */
12
+ path?: string;
13
+ }
14
+ /**
15
+ * Tokens in one JSON file, written atomically with owner-only permissions,
16
+ * for a single process. Stores of one process on the same file take turns,
17
+ * so a Bluesky session and a Threads token kept in one file never overwrite
18
+ * each other. Several processes need `PostgresTokenStore`.
19
+ */
20
+ export declare class FileTokenStore implements TokenStore {
21
+ readonly path: string;
22
+ private readonly queue;
23
+ constructor(options?: FileTokenStoreOptions);
24
+ get(key: string): Promise<StoredToken | undefined>;
25
+ set(key: string, token: StoredToken): Promise<void>;
26
+ delete(key: string): Promise<void>;
27
+ private read;
28
+ }
29
+ export interface PostgresTokenStoreOptions {
30
+ client: PgClient;
31
+ /** Table name (default `cogitator_tokens`), created on first use. */
32
+ table?: string;
33
+ }
34
+ /** Tokens in a Postgres table, shared by every process that renews them. */
35
+ export declare class PostgresTokenStore implements TokenStore {
36
+ private readonly client;
37
+ private readonly table;
38
+ private ready?;
39
+ constructor(options: PostgresTokenStoreOptions);
40
+ get(key: string): Promise<StoredToken | undefined>;
41
+ set(key: string, token: StoredToken): Promise<void>;
42
+ delete(key: string): Promise<void>;
43
+ private ensureTable;
44
+ }
45
+ //# sourceMappingURL=token-store.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"token-store.d.ts","sourceRoot":"","sources":["../../src/feeds/token-store.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACnE,OAAO,EAEL,KAAK,QAAQ,EAKd,MAAM,WAAW,CAAC;AAYnB,4FAA4F;AAC5F,qBAAa,gBAAiB,YAAW,UAAU;IACjD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkC;IAEnD,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAKlD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAInD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAGzC;AAED,MAAM,WAAW,qBAAqB;IACpC,sFAAsF;IACtF,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED;;;;;GAKG;AACH,qBAAa,cAAe,YAAW,UAAU;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAc;gBAExB,OAAO,GAAE,qBAA0B;IAKzC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAIlD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAQnD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAS1B,IAAI;CAkBnB;AAED,MAAM,WAAW,yBAAyB;IACxC,MAAM,EAAE,QAAQ,CAAC;IACjB,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,4EAA4E;AAC5E,qBAAa,kBAAmB,YAAW,UAAU;IACnD,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAW;IAClC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,KAAK,CAAC,CAAgB;gBAElB,OAAO,EAAE,yBAAyB;IAOxC,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC;IAiBlD,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IAYnD,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAKxC,OAAO,CAAC,WAAW;CAgBpB"}