@cogitator-ai/channels 0.9.2 → 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.
- package/README.md +101 -11
- package/dist/assistant-config.d.ts +16 -0
- package/dist/assistant-config.d.ts.map +1 -1
- package/dist/assistant-config.js +22 -0
- package/dist/assistant-config.js.map +1 -1
- package/dist/channels/bluesky/account.d.ts +18 -0
- package/dist/channels/bluesky/account.d.ts.map +1 -0
- package/dist/channels/bluesky/account.js +25 -0
- package/dist/channels/bluesky/account.js.map +1 -0
- package/dist/channels/bluesky/channel.d.ts +89 -0
- package/dist/channels/bluesky/channel.d.ts.map +1 -0
- package/dist/channels/bluesky/channel.js +427 -0
- package/dist/channels/bluesky/channel.js.map +1 -0
- package/dist/channels/bluesky/config.d.ts +16 -0
- package/dist/channels/bluesky/config.d.ts.map +1 -0
- package/dist/channels/bluesky/config.js +2 -0
- package/dist/channels/bluesky/config.js.map +1 -0
- package/dist/channels/bluesky/connection.d.ts +40 -0
- package/dist/channels/bluesky/connection.d.ts.map +1 -0
- package/dist/channels/bluesky/connection.js +159 -0
- package/dist/channels/bluesky/connection.js.map +1 -0
- package/dist/channels/bluesky/feed.d.ts +59 -0
- package/dist/channels/bluesky/feed.d.ts.map +1 -0
- package/dist/channels/bluesky/feed.js +324 -0
- package/dist/channels/bluesky/feed.js.map +1 -0
- package/dist/channels/bluesky/index.d.ts +5 -0
- package/dist/channels/bluesky/index.d.ts.map +1 -0
- package/dist/channels/bluesky/index.js +4 -0
- package/dist/channels/bluesky/index.js.map +1 -0
- package/dist/channels/threads/account.d.ts +42 -0
- package/dist/channels/threads/account.d.ts.map +1 -0
- package/dist/channels/threads/account.js +64 -0
- package/dist/channels/threads/account.js.map +1 -0
- package/dist/channels/threads/channel.d.ts +119 -0
- package/dist/channels/threads/channel.d.ts.map +1 -0
- package/dist/channels/threads/channel.js +405 -0
- package/dist/channels/threads/channel.js.map +1 -0
- package/dist/channels/threads/client.d.ts +42 -0
- package/dist/channels/threads/client.d.ts.map +1 -0
- package/dist/channels/threads/client.js +97 -0
- package/dist/channels/threads/client.js.map +1 -0
- package/dist/channels/threads/feed.d.ts +74 -0
- package/dist/channels/threads/feed.d.ts.map +1 -0
- package/dist/channels/threads/feed.js +372 -0
- package/dist/channels/threads/feed.js.map +1 -0
- package/dist/channels/threads/index.d.ts +6 -0
- package/dist/channels/threads/index.d.ts.map +1 -0
- package/dist/channels/threads/index.js +6 -0
- package/dist/channels/threads/index.js.map +1 -0
- package/dist/channels/threads/token.d.ts +51 -0
- package/dist/channels/threads/token.d.ts.map +1 -0
- package/dist/channels/threads/token.js +128 -0
- package/dist/channels/threads/token.js.map +1 -0
- package/dist/feeds/attachments.d.ts +15 -0
- package/dist/feeds/attachments.d.ts.map +1 -0
- package/dist/feeds/attachments.js +85 -0
- package/dist/feeds/attachments.js.map +1 -0
- package/dist/feeds/errors.d.ts +39 -0
- package/dist/feeds/errors.d.ts.map +1 -0
- package/dist/feeds/errors.js +62 -0
- package/dist/feeds/errors.js.map +1 -0
- package/dist/feeds/index.d.ts +8 -0
- package/dist/feeds/index.d.ts.map +1 -0
- package/dist/feeds/index.js +6 -0
- package/dist/feeds/index.js.map +1 -0
- package/dist/feeds/publish-store.d.ts +148 -0
- package/dist/feeds/publish-store.d.ts.map +1 -0
- package/dist/feeds/publish-store.js +345 -0
- package/dist/feeds/publish-store.js.map +1 -0
- package/dist/feeds/publisher.d.ts +118 -0
- package/dist/feeds/publisher.d.ts.map +1 -0
- package/dist/feeds/publisher.js +308 -0
- package/dist/feeds/publisher.js.map +1 -0
- package/dist/feeds/storage.d.ts +33 -0
- package/dist/feeds/storage.d.ts.map +1 -0
- package/dist/feeds/storage.js +61 -0
- package/dist/feeds/storage.js.map +1 -0
- package/dist/feeds/text.d.ts +26 -0
- package/dist/feeds/text.d.ts.map +1 -0
- package/dist/feeds/text.js +126 -0
- package/dist/feeds/text.js.map +1 -0
- package/dist/feeds/token-store.d.ts +45 -0
- package/dist/feeds/token-store.d.ts.map +1 -0
- package/dist/feeds/token-store.js +128 -0
- package/dist/feeds/token-store.js.map +1 -0
- package/dist/formatters/markdown.d.ts.map +1 -1
- package/dist/formatters/markdown.js +6 -0
- package/dist/formatters/markdown.js.map +1 -1
- package/dist/formatters/plain-text.d.ts +7 -0
- package/dist/formatters/plain-text.d.ts.map +1 -0
- package/dist/formatters/plain-text.js +38 -0
- package/dist/formatters/plain-text.js.map +1 -0
- package/dist/gateway.js +1 -1
- package/dist/gateway.js.map +1 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/runtime-builder.d.ts.map +1 -1
- package/dist/runtime-builder.js +54 -0
- package/dist/runtime-builder.js.map +1 -1
- package/package.json +23 -15
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { ThreadsAccount } from './account.js';
|
|
2
|
+
export { THREADS_API, ThreadsClient, threadsErrorCode } from './client.js';
|
|
3
|
+
export { THREADS_LIMITS, ThreadsFeed, threadsFeed } from './feed.js';
|
|
4
|
+
export { ThreadsTokenManager } from './token.js';
|
|
5
|
+
export { ThreadsChannel, threadsChannel, verifyThreadsSignature, } from './channel.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/channels/threads/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAA6B,MAAM,WAAW,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,gBAAgB,EAAsB,MAAM,UAAU,CAAC;AAC5F,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,WAAW,EAA0B,MAAM,QAAQ,CAAC;AAC1F,OAAO,EAAE,mBAAmB,EAA4B,MAAM,SAAS,CAAC;AACxE,OAAO,EACL,cAAc,EACd,cAAc,EACd,sBAAsB,GAKvB,MAAM,WAAW,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { StoredToken, TokenStore } from '@cogitator-ai/types';
|
|
2
|
+
import type { ThreadsClient } from './client.js';
|
|
3
|
+
export interface ThreadsTokenOptions {
|
|
4
|
+
/** A long-lived token to start from when the store holds none. */
|
|
5
|
+
accessToken?: string;
|
|
6
|
+
/** When `accessToken` expires, where known. Otherwise the first renewal finds out. */
|
|
7
|
+
accessTokenExpiresAt?: Date | number;
|
|
8
|
+
/** Where the token is kept and renewed (default in memory). */
|
|
9
|
+
store?: TokenStore;
|
|
10
|
+
/** The token's key in the store (default `threads`). */
|
|
11
|
+
storeKey?: string;
|
|
12
|
+
/** How long before it expires the token is renewed (default seven days). */
|
|
13
|
+
renewBefore?: number;
|
|
14
|
+
/** Called with the new token after each renewal. */
|
|
15
|
+
onTokenRenewed?: (token: StoredToken) => void | Promise<void>;
|
|
16
|
+
now?: () => number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A Threads long-lived token kept fresh. Threads renews a token that is at
|
|
20
|
+
* least a day old and not yet expired, for sixty days. One left alone
|
|
21
|
+
* expires and cannot be renewed. The manager renews it `renewBefore` its
|
|
22
|
+
* end, checks daily once started, and before each use. A token whose expiry
|
|
23
|
+
* is unknown is renewed at the first chance, which reveals it. Calls made at
|
|
24
|
+
* the same time share one renewal. Before renewing, the manager rereads the
|
|
25
|
+
* store, so a token another process renewed is used as it is. Two processes
|
|
26
|
+
* renewing at the same moment both get valid tokens.
|
|
27
|
+
*/
|
|
28
|
+
export declare class ThreadsTokenManager {
|
|
29
|
+
private readonly client;
|
|
30
|
+
private readonly options;
|
|
31
|
+
private readonly store;
|
|
32
|
+
private readonly key;
|
|
33
|
+
private readonly renewBefore;
|
|
34
|
+
private readonly now;
|
|
35
|
+
private nextAttemptAt;
|
|
36
|
+
private renewing?;
|
|
37
|
+
private timer?;
|
|
38
|
+
constructor(client: ThreadsClient, options: ThreadsTokenOptions);
|
|
39
|
+
/** A token to call the API with, renewed first when it is due. */
|
|
40
|
+
token(): Promise<string>;
|
|
41
|
+
/** Renews the token now, whatever its age. */
|
|
42
|
+
renew(): Promise<StoredToken>;
|
|
43
|
+
private renewOnce;
|
|
44
|
+
/** Checks the token every day until `stop()`. */
|
|
45
|
+
start(): void;
|
|
46
|
+
stop(): void;
|
|
47
|
+
private current;
|
|
48
|
+
private due;
|
|
49
|
+
private doRenew;
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=token.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.d.ts","sourceRoot":"","sources":["../../../src/channels/threads/token.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAGnE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAK9C,MAAM,WAAW,mBAAmB;IAClC,kEAAkE;IAClE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,sFAAsF;IACtF,oBAAoB,CAAC,EAAE,IAAI,GAAG,MAAM,CAAC;IACrC,+DAA+D;IAC/D,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,4EAA4E;IAC5E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oDAAoD;IACpD,cAAc,CAAC,EAAE,CAAC,KAAK,EAAE,WAAW,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9D,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,qBAAa,mBAAmB;IAU5B,OAAO,CAAC,QAAQ,CAAC,MAAM;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO;IAV1B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAa;IACnC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAS;IAC7B,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAS;IACrC,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IACnC,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,QAAQ,CAAC,CAAuB;IACxC,OAAO,CAAC,KAAK,CAAC,CAAiC;gBAG5B,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,mBAAmB;IAQ/C,kEAAkE;IAC5D,KAAK,IAAI,OAAO,CAAC,MAAM,CAAC;IAoB9B,8CAA8C;IAC9C,KAAK,IAAI,OAAO,CAAC,WAAW,CAAC;IAI7B,OAAO,CAAC,SAAS;IAOjB,iDAAiD;IACjD,KAAK,IAAI,IAAI;IAUb,IAAI,IAAI,IAAI;YAKE,OAAO;IAsBrB,OAAO,CAAC,GAAG;YAMG,OAAO;CA4BtB"}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { FeedError } from '../../feeds/errors.js';
|
|
2
|
+
import { MemoryTokenStore } from '../../feeds/token-store.js';
|
|
3
|
+
const DAY = 24 * 60 * 60 * 1000;
|
|
4
|
+
const HOUR = 60 * 60 * 1000;
|
|
5
|
+
/**
|
|
6
|
+
* A Threads long-lived token kept fresh. Threads renews a token that is at
|
|
7
|
+
* least a day old and not yet expired, for sixty days. One left alone
|
|
8
|
+
* expires and cannot be renewed. The manager renews it `renewBefore` its
|
|
9
|
+
* end, checks daily once started, and before each use. A token whose expiry
|
|
10
|
+
* is unknown is renewed at the first chance, which reveals it. Calls made at
|
|
11
|
+
* the same time share one renewal. Before renewing, the manager rereads the
|
|
12
|
+
* store, so a token another process renewed is used as it is. Two processes
|
|
13
|
+
* renewing at the same moment both get valid tokens.
|
|
14
|
+
*/
|
|
15
|
+
export class ThreadsTokenManager {
|
|
16
|
+
client;
|
|
17
|
+
options;
|
|
18
|
+
store;
|
|
19
|
+
key;
|
|
20
|
+
renewBefore;
|
|
21
|
+
now;
|
|
22
|
+
nextAttemptAt = 0;
|
|
23
|
+
renewing;
|
|
24
|
+
timer;
|
|
25
|
+
constructor(client, options) {
|
|
26
|
+
this.client = client;
|
|
27
|
+
this.options = options;
|
|
28
|
+
this.store = options.store ?? new MemoryTokenStore();
|
|
29
|
+
this.key = options.storeKey ?? 'threads';
|
|
30
|
+
this.renewBefore = options.renewBefore ?? 7 * DAY;
|
|
31
|
+
this.now = options.now ?? Date.now;
|
|
32
|
+
}
|
|
33
|
+
/** A token to call the API with, renewed first when it is due. */
|
|
34
|
+
async token() {
|
|
35
|
+
const current = await this.current();
|
|
36
|
+
if (current.expiresAt !== undefined && current.expiresAt <= this.now()) {
|
|
37
|
+
throw new FeedError('threads', 'token_expired', `The Threads token expired on ${new Date(current.expiresAt).toISOString()}: sign in again for a new long-lived token`);
|
|
38
|
+
}
|
|
39
|
+
if (this.due(current)) {
|
|
40
|
+
try {
|
|
41
|
+
return (await this.renewOnce(false)).value;
|
|
42
|
+
}
|
|
43
|
+
catch (error) {
|
|
44
|
+
if (error instanceof FeedError && error.code === 'token_expired')
|
|
45
|
+
throw error;
|
|
46
|
+
console.warn('[threads] Could not renew the token, using the current one:', error);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return current.value;
|
|
50
|
+
}
|
|
51
|
+
/** Renews the token now, whatever its age. */
|
|
52
|
+
renew() {
|
|
53
|
+
return this.renewOnce(true);
|
|
54
|
+
}
|
|
55
|
+
renewOnce(force) {
|
|
56
|
+
this.renewing ??= this.doRenew(force).finally(() => {
|
|
57
|
+
this.renewing = undefined;
|
|
58
|
+
});
|
|
59
|
+
return this.renewing;
|
|
60
|
+
}
|
|
61
|
+
/** Checks the token every day until `stop()`. */
|
|
62
|
+
start() {
|
|
63
|
+
if (this.timer)
|
|
64
|
+
return;
|
|
65
|
+
this.timer = setInterval(() => {
|
|
66
|
+
void this.token().catch((error) => console.error('[threads] The daily token check failed:', error));
|
|
67
|
+
}, DAY);
|
|
68
|
+
this.timer.unref?.();
|
|
69
|
+
}
|
|
70
|
+
stop() {
|
|
71
|
+
if (this.timer)
|
|
72
|
+
clearInterval(this.timer);
|
|
73
|
+
this.timer = undefined;
|
|
74
|
+
}
|
|
75
|
+
async current() {
|
|
76
|
+
const stored = await this.store.get(this.key);
|
|
77
|
+
if (stored)
|
|
78
|
+
return stored;
|
|
79
|
+
if (!this.options.accessToken) {
|
|
80
|
+
throw new FeedError('threads', 'auth', 'No Threads token: pass accessToken or put one in the token store');
|
|
81
|
+
}
|
|
82
|
+
const expiresAt = this.options.accessTokenExpiresAt;
|
|
83
|
+
const seeded = {
|
|
84
|
+
value: this.options.accessToken,
|
|
85
|
+
issuedAt: this.now(),
|
|
86
|
+
...(expiresAt !== undefined && {
|
|
87
|
+
expiresAt: typeof expiresAt === 'number' ? expiresAt : expiresAt.getTime(),
|
|
88
|
+
}),
|
|
89
|
+
};
|
|
90
|
+
await this.store.set(this.key, seeded);
|
|
91
|
+
return seeded;
|
|
92
|
+
}
|
|
93
|
+
due(token) {
|
|
94
|
+
if (this.now() < this.nextAttemptAt)
|
|
95
|
+
return false;
|
|
96
|
+
if (token.expiresAt === undefined)
|
|
97
|
+
return true;
|
|
98
|
+
return token.expiresAt - this.now() <= this.renewBefore && this.now() - token.issuedAt >= DAY;
|
|
99
|
+
}
|
|
100
|
+
async doRenew(force) {
|
|
101
|
+
const latest = await this.current();
|
|
102
|
+
if (!force && !this.due(latest))
|
|
103
|
+
return latest;
|
|
104
|
+
try {
|
|
105
|
+
const response = await this.client.get('refresh_access_token', latest.value, { grant_type: 'th_refresh_token' });
|
|
106
|
+
const now = this.now();
|
|
107
|
+
const renewed = {
|
|
108
|
+
value: response.access_token,
|
|
109
|
+
issuedAt: now,
|
|
110
|
+
...(response.expires_in !== undefined && { expiresAt: now + response.expires_in * 1000 }),
|
|
111
|
+
};
|
|
112
|
+
await this.store.set(this.key, renewed);
|
|
113
|
+
this.nextAttemptAt = 0;
|
|
114
|
+
try {
|
|
115
|
+
await this.options.onTokenRenewed?.(renewed);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
console.error('[threads] onTokenRenewed failed:', error);
|
|
119
|
+
}
|
|
120
|
+
return renewed;
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
this.nextAttemptAt = this.now() + HOUR;
|
|
124
|
+
throw error;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=token.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"token.js","sourceRoot":"","sources":["../../../src/channels/threads/token.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAG3D,MAAM,GAAG,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAChC,MAAM,IAAI,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AAkB5B;;;;;;;;;GASG;AACH,MAAM,OAAO,mBAAmB;IAUX;IACA;IAVF,KAAK,CAAa;IAClB,GAAG,CAAS;IACZ,WAAW,CAAS;IACpB,GAAG,CAAe;IAC3B,aAAa,GAAG,CAAC,CAAC;IAClB,QAAQ,CAAwB;IAChC,KAAK,CAAkC;IAE/C,YACmB,MAAqB,EACrB,OAA4B;QAD5B,WAAM,GAAN,MAAM,CAAe;QACrB,YAAO,GAAP,OAAO,CAAqB;QAE7C,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,IAAI,IAAI,gBAAgB,EAAE,CAAC;QACrD,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,QAAQ,IAAI,SAAS,CAAC;QACzC,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,CAAC,GAAG,GAAG,CAAC;QAClD,IAAI,CAAC,GAAG,GAAG,OAAO,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,CAAC;IACrC,CAAC;IAED,kEAAkE;IAClE,KAAK,CAAC,KAAK;QACT,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACrC,IAAI,OAAO,CAAC,SAAS,KAAK,SAAS,IAAI,OAAO,CAAC,SAAS,IAAI,IAAI,CAAC,GAAG,EAAE,EAAE,CAAC;YACvE,MAAM,IAAI,SAAS,CACjB,SAAS,EACT,eAAe,EACf,gCAAgC,IAAI,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,4CAA4C,CACtH,CAAC;QACJ,CAAC;QACD,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,EAAE,CAAC;YACtB,IAAI,CAAC;gBACH,OAAO,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC;YAC7C,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,IAAI,KAAK,YAAY,SAAS,IAAI,KAAK,CAAC,IAAI,KAAK,eAAe;oBAAE,MAAM,KAAK,CAAC;gBAC9E,OAAO,CAAC,IAAI,CAAC,6DAA6D,EAAE,KAAK,CAAC,CAAC;YACrF,CAAC;QACH,CAAC;QACD,OAAO,OAAO,CAAC,KAAK,CAAC;IACvB,CAAC;IAED,8CAA8C;IAC9C,KAAK;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;IAC9B,CAAC;IAEO,SAAS,CAAC,KAAc;QAC9B,IAAI,CAAC,QAAQ,KAAK,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,OAAO,CAAC,GAAG,EAAE;YACjD,IAAI,CAAC,QAAQ,GAAG,SAAS,CAAC;QAC5B,CAAC,CAAC,CAAC;QACH,OAAO,IAAI,CAAC,QAAQ,CAAC;IACvB,CAAC;IAED,iDAAiD;IACjD,KAAK;QACH,IAAI,IAAI,CAAC,KAAK;YAAE,OAAO;QACvB,IAAI,CAAC,KAAK,GAAG,WAAW,CAAC,GAAG,EAAE;YAC5B,KAAK,IAAI,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE,CACzC,OAAO,CAAC,KAAK,CAAC,yCAAyC,EAAE,KAAK,CAAC,CAChE,CAAC;QACJ,CAAC,EAAE,GAAG,CAAC,CAAC;QACR,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC;IACvB,CAAC;IAED,IAAI;QACF,IAAI,IAAI,CAAC,KAAK;YAAE,aAAa,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QAC1C,IAAI,CAAC,KAAK,GAAG,SAAS,CAAC;IACzB,CAAC;IAEO,KAAK,CAAC,OAAO;QACnB,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC9C,IAAI,MAAM;YAAE,OAAO,MAAM,CAAC;QAC1B,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;YAC9B,MAAM,IAAI,SAAS,CACjB,SAAS,EACT,MAAM,EACN,kEAAkE,CACnE,CAAC;QACJ,CAAC;QACD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,oBAAoB,CAAC;QACpD,MAAM,MAAM,GAAgB;YAC1B,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,WAAW;YAC/B,QAAQ,EAAE,IAAI,CAAC,GAAG,EAAE;YACpB,GAAG,CAAC,SAAS,KAAK,SAAS,IAAI;gBAC7B,SAAS,EAAE,OAAO,SAAS,KAAK,QAAQ,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,OAAO,EAAE;aAC3E,CAAC;SACH,CAAC;QACF,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,CAAC,CAAC;QACvC,OAAO,MAAM,CAAC;IAChB,CAAC;IAEO,GAAG,CAAC,KAAkB;QAC5B,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,aAAa;YAAE,OAAO,KAAK,CAAC;QAClD,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QAC/C,OAAO,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,WAAW,IAAI,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,QAAQ,IAAI,GAAG,CAAC;IAChG,CAAC;IAEO,KAAK,CAAC,OAAO,CAAC,KAAc;QAClC,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;QACpC,IAAI,CAAC,KAAK,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,MAAM,CAAC;YAAE,OAAO,MAAM,CAAC;QAC/C,IAAI,CAAC;YACH,MAAM,QAAQ,GAAG,MAAM,IAAI,CAAC,MAAM,CAAC,GAAG,CACpC,sBAAsB,EACtB,MAAM,CAAC,KAAK,EACZ,EAAE,UAAU,EAAE,kBAAkB,EAAE,CACnC,CAAC;YACF,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YACvB,MAAM,OAAO,GAAgB;gBAC3B,KAAK,EAAE,QAAQ,CAAC,YAAY;gBAC5B,QAAQ,EAAE,GAAG;gBACb,GAAG,CAAC,QAAQ,CAAC,UAAU,KAAK,SAAS,IAAI,EAAE,SAAS,EAAE,GAAG,GAAG,QAAQ,CAAC,UAAU,GAAG,IAAI,EAAE,CAAC;aAC1F,CAAC;YACF,MAAM,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC;YACxC,IAAI,CAAC,aAAa,GAAG,CAAC,CAAC;YACvB,IAAI,CAAC;gBACH,MAAM,IAAI,CAAC,OAAO,CAAC,cAAc,EAAE,CAAC,OAAO,CAAC,CAAC;YAC/C,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,OAAO,CAAC,KAAK,CAAC,kCAAkC,EAAE,KAAK,CAAC,CAAC;YAC3D,CAAC;YACD,OAAO,OAAO,CAAC;QACjB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,IAAI,CAAC,aAAa,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC;YACvC,MAAM,KAAK,CAAC;QACd,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Attachment } from '@cogitator-ai/types';
|
|
2
|
+
/**
|
|
3
|
+
* The bytes of an attachment: its buffer, or its URL downloaded. Fails with
|
|
4
|
+
* `invalid_post` when it is over `maxBytes`, without reading past the limit,
|
|
5
|
+
* or when the URL does not serve it, and with `unavailable` (or
|
|
6
|
+
* `rate_limited`) when its host fails for now, so the post is tried again.
|
|
7
|
+
*/
|
|
8
|
+
export declare function attachmentBytes(feed: string, attachment: Attachment, options: {
|
|
9
|
+
maxBytes: number;
|
|
10
|
+
fetch?: typeof fetch;
|
|
11
|
+
what: string;
|
|
12
|
+
}): Promise<Uint8Array>;
|
|
13
|
+
/** Fails with `invalid_post` unless the attachment is an image. */
|
|
14
|
+
export declare function assertImage(feed: string, attachment: Attachment, what: string): void;
|
|
15
|
+
//# sourceMappingURL=attachments.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachments.d.ts","sourceRoot":"","sources":["../../src/feeds/attachments.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAGtD;;;;;GAKG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,UAAU,EACtB,OAAO,EAAE;IAAE,QAAQ,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GAChE,OAAO,CAAC,UAAU,CAAC,CAqFrB;AAED,mEAAmE;AACnE,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAQpF"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { FeedError, retryAfterOf } from './errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* The bytes of an attachment: its buffer, or its URL downloaded. Fails with
|
|
4
|
+
* `invalid_post` when it is over `maxBytes`, without reading past the limit,
|
|
5
|
+
* or when the URL does not serve it, and with `unavailable` (or
|
|
6
|
+
* `rate_limited`) when its host fails for now, so the post is tried again.
|
|
7
|
+
*/
|
|
8
|
+
export async function attachmentBytes(feed, attachment, options) {
|
|
9
|
+
const tooLarge = (size) => new FeedError(feed, 'invalid_post', `${options.what} is ${size} bytes, the feed takes up to ${options.maxBytes}`);
|
|
10
|
+
if (attachment.buffer) {
|
|
11
|
+
if (attachment.buffer.byteLength > options.maxBytes)
|
|
12
|
+
throw tooLarge(attachment.buffer.byteLength);
|
|
13
|
+
return attachment.buffer;
|
|
14
|
+
}
|
|
15
|
+
if (!attachment.url) {
|
|
16
|
+
throw new FeedError(feed, 'invalid_post', `${options.what} has neither a buffer nor a url`);
|
|
17
|
+
}
|
|
18
|
+
const unreachable = (error) => new FeedError(feed, 'unavailable', `Could not download ${options.what} from ${attachment.url}: ${error instanceof Error ? error.message : String(error)}`, { cause: error });
|
|
19
|
+
let response;
|
|
20
|
+
try {
|
|
21
|
+
response = await (options.fetch ?? fetch)(attachment.url);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
throw unreachable(error);
|
|
25
|
+
}
|
|
26
|
+
if (!response.ok) {
|
|
27
|
+
const { status } = response;
|
|
28
|
+
const code = status === 429
|
|
29
|
+
? 'rate_limited'
|
|
30
|
+
: status === 408 || status >= 500
|
|
31
|
+
? 'unavailable'
|
|
32
|
+
: 'invalid_post';
|
|
33
|
+
const retryAfter = retryAfterOf(response.headers);
|
|
34
|
+
await response.body?.cancel().catch(() => { });
|
|
35
|
+
throw new FeedError(feed, code, `Could not download ${options.what} from ${attachment.url}: HTTP ${status}`, { status, ...(retryAfter !== undefined && { retryAfter }) });
|
|
36
|
+
}
|
|
37
|
+
const declared = Number(response.headers.get('content-length'));
|
|
38
|
+
if (Number.isFinite(declared) && declared > options.maxBytes) {
|
|
39
|
+
await response.body?.cancel();
|
|
40
|
+
throw tooLarge(declared);
|
|
41
|
+
}
|
|
42
|
+
const reader = response.body?.getReader();
|
|
43
|
+
if (!reader) {
|
|
44
|
+
try {
|
|
45
|
+
return new Uint8Array(await response.arrayBuffer());
|
|
46
|
+
}
|
|
47
|
+
catch (error) {
|
|
48
|
+
throw unreachable(error);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
const chunks = [];
|
|
52
|
+
let size = 0;
|
|
53
|
+
for (;;) {
|
|
54
|
+
let chunk;
|
|
55
|
+
try {
|
|
56
|
+
chunk = await reader.read();
|
|
57
|
+
}
|
|
58
|
+
catch (error) {
|
|
59
|
+
throw unreachable(error);
|
|
60
|
+
}
|
|
61
|
+
const { done, value } = chunk;
|
|
62
|
+
if (done)
|
|
63
|
+
break;
|
|
64
|
+
size += value.byteLength;
|
|
65
|
+
if (size > options.maxBytes) {
|
|
66
|
+
await reader.cancel();
|
|
67
|
+
throw tooLarge(size);
|
|
68
|
+
}
|
|
69
|
+
chunks.push(value);
|
|
70
|
+
}
|
|
71
|
+
const bytes = new Uint8Array(size);
|
|
72
|
+
let offset = 0;
|
|
73
|
+
for (const chunk of chunks) {
|
|
74
|
+
bytes.set(chunk, offset);
|
|
75
|
+
offset += chunk.byteLength;
|
|
76
|
+
}
|
|
77
|
+
return bytes;
|
|
78
|
+
}
|
|
79
|
+
/** Fails with `invalid_post` unless the attachment is an image. */
|
|
80
|
+
export function assertImage(feed, attachment, what) {
|
|
81
|
+
if (attachment.type !== 'image' || !attachment.mimeType.startsWith('image/')) {
|
|
82
|
+
throw new FeedError(feed, 'invalid_post', `${what} must be an image, got ${attachment.type} (${attachment.mimeType})`);
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=attachments.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attachments.js","sourceRoot":"","sources":["../../src/feeds/attachments.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export type FeedErrorCode =
|
|
2
|
+
/** The credentials are wrong or lack a permission. */
|
|
3
|
+
'auth'
|
|
4
|
+
/** The token or session expired and cannot be renewed. */
|
|
5
|
+
| 'token_expired'
|
|
6
|
+
/** Too many requests: try again after `retryAfter`. */
|
|
7
|
+
| 'rate_limited'
|
|
8
|
+
/** The account used up its publishing quota for now. */
|
|
9
|
+
| 'quota_exceeded'
|
|
10
|
+
/** The post breaks a rule of the feed: too long, too many links, a bad tag or image. */
|
|
11
|
+
| 'invalid_post'
|
|
12
|
+
/** The post or account it refers to does not exist. */
|
|
13
|
+
| 'not_found'
|
|
14
|
+
/** The feed failed or did not answer: try again. */
|
|
15
|
+
| 'unavailable' | 'unknown';
|
|
16
|
+
/** A failure of a feed, with whether and when trying again can help. */
|
|
17
|
+
export declare class FeedError extends Error {
|
|
18
|
+
readonly feed: string;
|
|
19
|
+
readonly code: FeedErrorCode;
|
|
20
|
+
/** The HTTP status, where the feed answered with one. */
|
|
21
|
+
readonly status?: number;
|
|
22
|
+
/** How long to wait before trying again, in ms, where the feed said. */
|
|
23
|
+
readonly retryAfter?: number;
|
|
24
|
+
constructor(feed: string, code: FeedErrorCode, message: string, options?: {
|
|
25
|
+
status?: number;
|
|
26
|
+
retryAfter?: number;
|
|
27
|
+
cause?: unknown;
|
|
28
|
+
});
|
|
29
|
+
/** Whether publishing again later can succeed. */
|
|
30
|
+
get retryable(): boolean;
|
|
31
|
+
}
|
|
32
|
+
/** The error code an HTTP status means, where the body says nothing more precise. */
|
|
33
|
+
export declare function feedErrorCode(status: number): FeedErrorCode;
|
|
34
|
+
/**
|
|
35
|
+
* How long a `Retry-After` header (seconds or an HTTP date) or a
|
|
36
|
+
* `ratelimit-reset` header (epoch seconds) asks to wait, in ms.
|
|
37
|
+
*/
|
|
38
|
+
export declare function retryAfterOf(headers: Headers, now?: number): number | undefined;
|
|
39
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../../src/feeds/errors.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,aAAa;AACvB,sDAAsD;AACpD,MAAM;AACR,0DAA0D;GACxD,eAAe;AACjB,uDAAuD;GACrD,cAAc;AAChB,wDAAwD;GACtD,gBAAgB;AAClB,wFAAwF;GACtF,cAAc;AAChB,uDAAuD;GACrD,WAAW;AACb,oDAAoD;GAClD,aAAa,GACb,SAAS,CAAC;AAQd,wEAAwE;AACxE,qBAAa,SAAU,SAAQ,KAAK;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,aAAa,CAAC;IAC7B,yDAAyD;IACzD,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,wEAAwE;IACxE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;gBAG3B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,aAAa,EACnB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QAAE,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,UAAU,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO;IAUzE,kDAAkD;IAClD,IAAI,SAAS,IAAI,OAAO,CAEvB;CACF;AAED,qFAAqF;AACrF,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,GAAG,aAAa,CAO3D;AAED;;;GAGG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,EAAE,GAAG,SAAa,GAAG,MAAM,GAAG,SAAS,CAWnF"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
const RETRYABLE = new Set([
|
|
2
|
+
'rate_limited',
|
|
3
|
+
'quota_exceeded',
|
|
4
|
+
'unavailable',
|
|
5
|
+
]);
|
|
6
|
+
/** A failure of a feed, with whether and when trying again can help. */
|
|
7
|
+
export class FeedError extends Error {
|
|
8
|
+
feed;
|
|
9
|
+
code;
|
|
10
|
+
/** The HTTP status, where the feed answered with one. */
|
|
11
|
+
status;
|
|
12
|
+
/** How long to wait before trying again, in ms, where the feed said. */
|
|
13
|
+
retryAfter;
|
|
14
|
+
constructor(feed, code, message, options = {}) {
|
|
15
|
+
super(`[${feed}] ${message}`, { cause: options.cause });
|
|
16
|
+
this.name = 'FeedError';
|
|
17
|
+
this.feed = feed;
|
|
18
|
+
this.code = code;
|
|
19
|
+
if (options.status !== undefined)
|
|
20
|
+
this.status = options.status;
|
|
21
|
+
if (options.retryAfter !== undefined)
|
|
22
|
+
this.retryAfter = options.retryAfter;
|
|
23
|
+
}
|
|
24
|
+
/** Whether publishing again later can succeed. */
|
|
25
|
+
get retryable() {
|
|
26
|
+
return RETRYABLE.has(this.code);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/** The error code an HTTP status means, where the body says nothing more precise. */
|
|
30
|
+
export function feedErrorCode(status) {
|
|
31
|
+
if (status === 401 || status === 403)
|
|
32
|
+
return 'auth';
|
|
33
|
+
if (status === 404)
|
|
34
|
+
return 'not_found';
|
|
35
|
+
if (status === 429)
|
|
36
|
+
return 'rate_limited';
|
|
37
|
+
if (status === 400 || status === 413 || status === 422)
|
|
38
|
+
return 'invalid_post';
|
|
39
|
+
if (status >= 500)
|
|
40
|
+
return 'unavailable';
|
|
41
|
+
return 'unknown';
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* How long a `Retry-After` header (seconds or an HTTP date) or a
|
|
45
|
+
* `ratelimit-reset` header (epoch seconds) asks to wait, in ms.
|
|
46
|
+
*/
|
|
47
|
+
export function retryAfterOf(headers, now = Date.now()) {
|
|
48
|
+
const after = headers.get('retry-after');
|
|
49
|
+
if (after) {
|
|
50
|
+
const seconds = Number(after);
|
|
51
|
+
if (Number.isFinite(seconds))
|
|
52
|
+
return Math.max(0, seconds * 1000);
|
|
53
|
+
const date = Date.parse(after);
|
|
54
|
+
if (Number.isFinite(date))
|
|
55
|
+
return Math.max(0, date - now);
|
|
56
|
+
}
|
|
57
|
+
const reset = Number(headers.get('ratelimit-reset'));
|
|
58
|
+
if (Number.isFinite(reset) && reset > 0)
|
|
59
|
+
return Math.max(0, reset * 1000 - now);
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/feeds/errors.ts"],"names":[],"mappings":"AAiBA,MAAM,SAAS,GAA+B,IAAI,GAAG,CAAC;IACpD,cAAc;IACd,gBAAgB;IAChB,aAAa;CACd,CAAC,CAAC;AAEH,wEAAwE;AACxE,MAAM,OAAO,SAAU,SAAQ,KAAK;IACzB,IAAI,CAAS;IACb,IAAI,CAAgB;IAC7B,yDAAyD;IAChD,MAAM,CAAU;IACzB,wEAAwE;IAC/D,UAAU,CAAU;IAE7B,YACE,IAAY,EACZ,IAAmB,EACnB,OAAe,EACf,UAAqE,EAAE;QAEvE,KAAK,CAAC,IAAI,IAAI,KAAK,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,CAAC;QACxD,IAAI,CAAC,IAAI,GAAG,WAAW,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,OAAO,CAAC,MAAM,KAAK,SAAS;YAAE,IAAI,CAAC,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;QAC/D,IAAI,OAAO,CAAC,UAAU,KAAK,SAAS;YAAE,IAAI,CAAC,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC;IAC7E,CAAC;IAED,kDAAkD;IAClD,IAAI,SAAS;QACX,OAAO,SAAS,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAClC,CAAC;CACF;AAED,qFAAqF;AACrF,MAAM,UAAU,aAAa,CAAC,MAAc;IAC1C,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,MAAM,CAAC;IACpD,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,WAAW,CAAC;IACvC,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,cAAc,CAAC;IAC1C,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG;QAAE,OAAO,cAAc,CAAC;IAC9E,IAAI,MAAM,IAAI,GAAG;QAAE,OAAO,aAAa,CAAC;IACxC,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,OAAgB,EAAE,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE;IAC7D,MAAM,KAAK,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IACzC,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,OAAO,GAAG,MAAM,CAAC,KAAK,CAAC,CAAC;QAC9B,IAAI,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;YAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC,CAAC;QACjE,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC/B,IAAI,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC;YAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,GAAG,GAAG,CAAC,CAAC;IAC5D,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,iBAAiB,CAAC,CAAC,CAAC;IACrD,IAAI,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,KAAK,GAAG,IAAI,GAAG,GAAG,CAAC,CAAC;IAChF,OAAO,SAAS,CAAC;AACnB,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { FeedError, feedErrorCode, retryAfterOf } from './errors.js';
|
|
2
|
+
export type { FeedErrorCode } from './errors.js';
|
|
3
|
+
export { FeedPublisher, type FeedFailedEvent, type FeedPublishedEvent, type FeedPublisherOptions, type FeedRetryOptions, type PublishOptions, type PublishOutcome, } from './publisher.js';
|
|
4
|
+
export { FilePublishStore, MemoryPublishStore, PostgresPublishStore, decodeJob, encodeJob, isPending, nextDueAt, type FeedDelivery, type FilePublishStoreOptions, type JobClaim, type PostgresPublishStoreOptions, type PublishJob, type PublishStore, } from './publish-store.js';
|
|
5
|
+
export { FileTokenStore, MemoryTokenStore, PostgresTokenStore, type FileTokenStoreOptions, type PostgresTokenStoreOptions, } from './token-store.js';
|
|
6
|
+
export type { PgClient } from './storage.js';
|
|
7
|
+
export { fitText, graphemeLength, graphemes, splitText, threadsLength, type TextMeasure, } from './text.js';
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/feeds/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAClE,YAAY,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAC9C,OAAO,EACL,aAAa,EACb,KAAK,eAAe,EACpB,KAAK,kBAAkB,EACvB,KAAK,oBAAoB,EACzB,KAAK,gBAAgB,EACrB,KAAK,cAAc,EACnB,KAAK,cAAc,GACpB,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,EACpB,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,EACT,KAAK,YAAY,EACjB,KAAK,uBAAuB,EAC5B,KAAK,QAAQ,EACb,KAAK,2BAA2B,EAChC,KAAK,UAAU,EACf,KAAK,YAAY,GAClB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,kBAAkB,EAClB,KAAK,qBAAqB,EAC1B,KAAK,yBAAyB,GAC/B,MAAM,eAAe,CAAC;AACvB,YAAY,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EACL,OAAO,EACP,cAAc,EACd,SAAS,EACT,SAAS,EACT,aAAa,EACb,KAAK,WAAW,GACjB,MAAM,QAAQ,CAAC"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { FeedError, feedErrorCode, retryAfterOf } from './errors.js';
|
|
2
|
+
export { FeedPublisher, } from './publisher.js';
|
|
3
|
+
export { FilePublishStore, MemoryPublishStore, PostgresPublishStore, decodeJob, encodeJob, isPending, nextDueAt, } from './publish-store.js';
|
|
4
|
+
export { FileTokenStore, MemoryTokenStore, PostgresTokenStore, } from './token-store.js';
|
|
5
|
+
export { fitText, graphemeLength, graphemes, splitText, threadsLength, } from './text.js';
|
|
6
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/feeds/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAElE,OAAO,EACL,aAAa,GAOd,MAAM,aAAa,CAAC;AACrB,OAAO,EACL,gBAAgB,EAChB,kBAAkB,EAClB,oBAAoB,EACpB,SAAS,EACT,SAAS,EACT,SAAS,EACT,SAAS,GAOV,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,cAAc,EACd,gBAAgB,EAChB,kBAAkB,GAGnB,MAAM,eAAe,CAAC;AAEvB,OAAO,EACL,OAAO,EACP,cAAc,EACd,SAAS,EACT,SAAS,EACT,aAAa,GAEd,MAAM,QAAQ,CAAC"}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import type { FeedPost } from '@cogitator-ai/types';
|
|
2
|
+
import type { FeedErrorCode } from './errors.js';
|
|
3
|
+
import { type PgClient } from './storage.js';
|
|
4
|
+
/** How one feed of a job is doing. */
|
|
5
|
+
export interface FeedDelivery {
|
|
6
|
+
feed: string;
|
|
7
|
+
status: 'pending' | 'published' | 'failed';
|
|
8
|
+
/** Attempts that could have published: a wait for a full quota is not one. */
|
|
9
|
+
attempts: number;
|
|
10
|
+
/** The feed's idempotency key, kept from before the first attempt until it is published. */
|
|
11
|
+
idempotencyKey?: string;
|
|
12
|
+
/** When to try next, in ms since the epoch, while pending. */
|
|
13
|
+
nextAttemptAt: number;
|
|
14
|
+
post?: {
|
|
15
|
+
id: string;
|
|
16
|
+
url: string;
|
|
17
|
+
publishedAt: number;
|
|
18
|
+
};
|
|
19
|
+
error?: {
|
|
20
|
+
code: FeedErrorCode;
|
|
21
|
+
message: string;
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** A post on its way to one or more feeds. */
|
|
25
|
+
export interface PublishJob {
|
|
26
|
+
id: string;
|
|
27
|
+
/** Publishing twice with the same key publishes once. */
|
|
28
|
+
key?: string;
|
|
29
|
+
post: FeedPost;
|
|
30
|
+
createdAt: number;
|
|
31
|
+
deliveries: FeedDelivery[];
|
|
32
|
+
}
|
|
33
|
+
/** Whether a job still has a feed to publish to. */
|
|
34
|
+
export declare function isPending(job: PublishJob): boolean;
|
|
35
|
+
/** When the job's next pending delivery is due, or `undefined` when it is done. */
|
|
36
|
+
export declare function nextDueAt(job: PublishJob): number | undefined;
|
|
37
|
+
/** A worker's hold on a job: who holds it, and for how long a claim or an extension lasts. */
|
|
38
|
+
export interface JobClaim {
|
|
39
|
+
owner: string;
|
|
40
|
+
/** In ms. */
|
|
41
|
+
ttl: number;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Where a publisher keeps its jobs, so scheduled and retried posts survive a
|
|
45
|
+
* restart. A worker claims a job before it publishes it, extends the claim
|
|
46
|
+
* while it works, and can save the job only while the claim is its own, so
|
|
47
|
+
* a worker that lost its claim never overwrites the one that took over.
|
|
48
|
+
*/
|
|
49
|
+
export interface PublishStore {
|
|
50
|
+
/** Adds `job`, unless one with the same key exists: that one is returned instead. */
|
|
51
|
+
add(job: PublishJob): Promise<PublishJob>;
|
|
52
|
+
get(id: string): Promise<PublishJob | undefined>;
|
|
53
|
+
/** Claims the job with the earliest delivery due by `now` that no one holds. */
|
|
54
|
+
claimNext(now: number, claim: JobClaim): Promise<PublishJob | undefined>;
|
|
55
|
+
/** Claims one job, `false` when someone holds it or it is missing. */
|
|
56
|
+
claim(id: string, claim: JobClaim): Promise<boolean>;
|
|
57
|
+
/** Extends `claim.owner`'s claim by `claim.ttl` from now, `false` when it lost it. */
|
|
58
|
+
extend(id: string, claim: JobClaim): Promise<boolean>;
|
|
59
|
+
/** Saves a job while `owner` holds it, `false` when the claim was lost and nothing was saved. */
|
|
60
|
+
save(job: PublishJob, owner: string): Promise<boolean>;
|
|
61
|
+
/** Releases `owner`'s claim on a job. */
|
|
62
|
+
release(id: string, owner: string): Promise<void>;
|
|
63
|
+
list(filter?: {
|
|
64
|
+
pending?: boolean;
|
|
65
|
+
}): Promise<PublishJob[]>;
|
|
66
|
+
/** Removes a job no worker holds, `false` when it is missing or held. */
|
|
67
|
+
remove(id: string): Promise<boolean>;
|
|
68
|
+
}
|
|
69
|
+
/** A job as JSON: attachment bytes become base64. */
|
|
70
|
+
export declare function encodeJob(job: PublishJob): string;
|
|
71
|
+
/** A job back from `encodeJob`. */
|
|
72
|
+
export declare function decodeJob(json: string | unknown): PublishJob;
|
|
73
|
+
/** Jobs in memory, for tests and a publisher that needs no restart safety. */
|
|
74
|
+
export declare class MemoryPublishStore implements PublishStore {
|
|
75
|
+
private readonly jobs;
|
|
76
|
+
private readonly claims;
|
|
77
|
+
constructor(now?: () => number);
|
|
78
|
+
add(job: PublishJob): Promise<PublishJob>;
|
|
79
|
+
get(id: string): Promise<PublishJob | undefined>;
|
|
80
|
+
claimNext(now: number, claim: JobClaim): Promise<PublishJob | undefined>;
|
|
81
|
+
claim(id: string, claim: JobClaim): Promise<boolean>;
|
|
82
|
+
extend(id: string, claim: JobClaim): Promise<boolean>;
|
|
83
|
+
save(job: PublishJob, owner: string): Promise<boolean>;
|
|
84
|
+
release(id: string, owner: string): Promise<void>;
|
|
85
|
+
list(filter?: {
|
|
86
|
+
pending?: boolean;
|
|
87
|
+
}): Promise<PublishJob[]>;
|
|
88
|
+
remove(id: string): Promise<boolean>;
|
|
89
|
+
}
|
|
90
|
+
export interface FilePublishStoreOptions {
|
|
91
|
+
/** The directory with one JSON file per job (default `~/.cogitator/feed-jobs`). */
|
|
92
|
+
directory?: string;
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Jobs as one JSON file each, written atomically, for a single publishing
|
|
96
|
+
* process: claims live in that process. Several workers need
|
|
97
|
+
* `PostgresPublishStore`.
|
|
98
|
+
*/
|
|
99
|
+
export declare class FilePublishStore implements PublishStore {
|
|
100
|
+
readonly directory: string;
|
|
101
|
+
private readonly queue;
|
|
102
|
+
private readonly claims;
|
|
103
|
+
constructor(options?: FilePublishStoreOptions, now?: () => number);
|
|
104
|
+
add(job: PublishJob): Promise<PublishJob>;
|
|
105
|
+
get(id: string): Promise<PublishJob | undefined>;
|
|
106
|
+
claimNext(now: number, claim: JobClaim): Promise<PublishJob | undefined>;
|
|
107
|
+
claim(id: string, claim: JobClaim): Promise<boolean>;
|
|
108
|
+
extend(id: string, claim: JobClaim): Promise<boolean>;
|
|
109
|
+
save(job: PublishJob, owner: string): Promise<boolean>;
|
|
110
|
+
release(id: string, owner: string): Promise<void>;
|
|
111
|
+
list(filter?: {
|
|
112
|
+
pending?: boolean;
|
|
113
|
+
}): Promise<PublishJob[]>;
|
|
114
|
+
remove(id: string): Promise<boolean>;
|
|
115
|
+
private fileOf;
|
|
116
|
+
private write;
|
|
117
|
+
private read;
|
|
118
|
+
private readAll;
|
|
119
|
+
}
|
|
120
|
+
export interface PostgresPublishStoreOptions {
|
|
121
|
+
client: PgClient;
|
|
122
|
+
/** Table name (default `cogitator_feed_jobs`), created on first use. */
|
|
123
|
+
table?: string;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Jobs in a Postgres table. Claims are leases on the database clock, taken
|
|
127
|
+
* with `FOR UPDATE SKIP LOCKED`, so several publishing workers split the due
|
|
128
|
+
* jobs between them, and a worker saves a job only while its lease holds.
|
|
129
|
+
*/
|
|
130
|
+
export declare class PostgresPublishStore implements PublishStore {
|
|
131
|
+
private readonly client;
|
|
132
|
+
private readonly table;
|
|
133
|
+
private ready?;
|
|
134
|
+
constructor(options: PostgresPublishStoreOptions);
|
|
135
|
+
add(job: PublishJob): Promise<PublishJob>;
|
|
136
|
+
get(id: string): Promise<PublishJob | undefined>;
|
|
137
|
+
claimNext(now: number, claim: JobClaim): Promise<PublishJob | undefined>;
|
|
138
|
+
claim(id: string, claim: JobClaim): Promise<boolean>;
|
|
139
|
+
extend(id: string, claim: JobClaim): Promise<boolean>;
|
|
140
|
+
save(job: PublishJob, owner: string): Promise<boolean>;
|
|
141
|
+
release(id: string, owner: string): Promise<void>;
|
|
142
|
+
list(filter?: {
|
|
143
|
+
pending?: boolean;
|
|
144
|
+
}): Promise<PublishJob[]>;
|
|
145
|
+
remove(id: string): Promise<boolean>;
|
|
146
|
+
private ensureTable;
|
|
147
|
+
}
|
|
148
|
+
//# sourceMappingURL=publish-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"publish-store.d.ts","sourceRoot":"","sources":["../../src/feeds/publish-store.ts"],"names":[],"mappings":"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"}
|