@framers/agentos-ext-channel-email 0.4.3 → 0.4.4
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 +3 -3
- package/dist/preferences/RecipientCapability.d.ts +5 -5
- package/dist/preferences/RecipientCapability.d.ts.map +1 -1
- package/dist/preferences/RecipientCapability.js +9 -4
- package/dist/preferences/RecipientCapability.js.map +1 -1
- package/dist/preferences/SuppressionStore.d.ts +2 -2
- package/dist/preferences/SuppressionStore.js +2 -2
- package/dist/preferences/database.d.ts +4 -2
- package/dist/preferences/database.d.ts.map +1 -1
- package/dist/preferences/database.js +9 -10
- package/dist/preferences/database.js.map +1 -1
- package/dist/preferences/index.d.ts +2 -2
- package/dist/preferences/index.js +2 -2
- package/manifest.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -95,7 +95,7 @@ The subject goes out on one line through both transports: each line break in it,
|
|
|
95
95
|
|
|
96
96
|
## Suppressions and unsubscribe tokens
|
|
97
97
|
|
|
98
|
-
Two stores a sender reads, each on the host's SQL database: `SqlSuppressionStore` keeps the addresses not to mail, each named by a hash of it, such as its SHA-256, and `SqlRecipientCapability` keeps opaque tokens, such as an unsubscribe link's, each naming a recipient and a scope. Both take [`@framers/sql-storage-adapter`](https://www.npmjs.com/package/@framers/sql-storage-adapter)'s adapter on SQLite or PostgreSQL, or any handle with its `run` and `get` (`?` placeholders, positional parameters), so the pack gains no dependency. A handle's `run` answers `changes`, the rows the statement wrote. A handle whose answer holds no such count, because it leaves `changes` out or gives something other than a whole number of 0 or more (the adapter's Capacitor handle gave an object before 0.6.14), still records, issues and revokes, but `suppress` then answers `false` and `revoke` 0, whatever they wrote.
|
|
98
|
+
Two stores a sender reads, each on the host's SQL database: `SqlSuppressionStore` keeps the addresses not to mail, each named by a hash of it, such as its SHA-256, and `SqlRecipientCapability` keeps opaque tokens, such as an unsubscribe link's, each naming a recipient and a scope. Both take [`@framers/sql-storage-adapter`](https://www.npmjs.com/package/@framers/sql-storage-adapter)'s adapter on SQLite or PostgreSQL, or any handle with its `run` and `get` (`?` placeholders, positional parameters), so the pack gains no dependency. The handle answers each `get` from the database: one that answers from a cache of its earlier reads, such as the adapter's deprecated `SupabaseAdapter` after `enableCache()`, can answer a row as it stood before a later write, so `find` could still answer a token after its `revoke`. A handle's `run` answers `changes`, the rows the statement wrote. A handle whose answer holds no such count, because it leaves `changes` out or gives something other than a whole number of 0 or more (the adapter's Capacitor handle gave an object before 0.6.14), still records, issues and revokes, but `suppress` then answers `false` and `revoke` 0, whatever they wrote.
|
|
99
99
|
|
|
100
100
|
```ts
|
|
101
101
|
import { createHash } from 'node:crypto';
|
|
@@ -127,9 +127,9 @@ const grant = await tokens.find(token); // { recipient: 'account-1', scope: 'wee
|
|
|
127
127
|
|
|
128
128
|
`suppress(scope, addressHash, reason, day)` records an address in a scope with its reason and its day (`YYYY-MM-DD`), and no end: the store has no call that lifts an address, so an operator lifts one by deleting its row through the same handle, as in `await db.run('DELETE FROM mail_suppressions WHERE scope = ? AND address_hash = ?', [DEFAULT_SUPPRESSION_SCOPE, addressHash])` for the example's prefix `mail`. The `?` placeholders are the adapter's, which binds the values given and on PostgreSQL writes `$1` and `$2` in their place. In a console, write the scope and the hash as quoted literals instead: PostgreSQL reads a `?` as an operator and refuses the statement, and the sqlite3 shell binds a `?` to NULL, so the statement deletes nothing. The reason is `bounce`, `complaint`, `suppressed` (the provider refused a message because its own list held the address) or `manual`. A second record of the address in the scope writes nothing and keeps the first, so a provider's repeated event is harmless; `suppress` answers `true` only for the call that recorded it, and two hosts recording one address at once leave one row. `isSuppressed(scope, addressHash)` answers for one scope. A scope is one list, and a host that keeps one passes `DEFAULT_SUPPRESSION_SCOPE` (`mail`) everywhere. `addressHash` is the address's hash as 64 lower-case hexadecimal characters, such as its SHA-256, which the host computes the same way wherever it reads or writes the record. Any other value, an address among them, throws a `TypeError` that holds none of it, so a mistake stores nothing and is never answered as not suppressed. The store takes any 64 lower-case hexadecimal characters, so the host chooses the hash. A plain SHA-256, as in the example, lets anyone who holds the table check an address they guess against it; a host that wants the table to tell nothing of an address it does not already hold computes HMAC-SHA256 under a secret key of its own instead, the same way at every reader and writer. `SUPPRESSION_REASONS` lists the four reasons, and it is frozen, since `suppress` checks a reason against it.
|
|
129
129
|
|
|
130
|
-
`issue(recipient, scope, nonce, key)` answers base64url of HMAC-SHA256 over the nonce under the key, 43 characters, and stores the token's SHA-256 beside the recipient and the scope: the token carries neither, and the table holds nothing it can be recovered from. The same nonce under the same key gives the same token, so each message takes a nonce of its own, such as its id. Issuing it again for the same recipient, scope and expiry writes nothing; for another recipient, scope or expiry it throws, and the first token keeps all three. The key is 32 random bytes or more, given as
|
|
130
|
+
`issue(recipient, scope, nonce, key)` answers base64url of HMAC-SHA256 over the nonce under the key, 43 characters, and stores the token's SHA-256 beside the recipient and the scope: the token carries neither, and the table holds nothing it can be recovered from. The same nonce under the same key gives the same token, so each message takes a nonce of its own, such as its id. Issuing it again for the same recipient, scope and expiry writes nothing; for another recipient, scope or expiry it throws, and the first token keeps all three. The key is 32 random bytes or more, given as a `Uint8Array` or a `Buffer` and kept secret, such as `crypto.randomBytes(32)` stored with the host's other secrets; the store cannot judge how random a key is. A key made in another realm, such as a `vm` context or a test runner's sandbox, is taken as the same bytes made here. A different key gives different tokens for the same nonces, and the same key the same tokens, whatever the prefix: the prefix keeps two hosts' tables apart, not their tokens. Two hosts on one database that share a key and use one nonce draw one token, and each one's `find` answers its own row for it, so a link one host sent could act on the other host's recipient. Each host takes a key of its own, or nonces no other host uses, such as its own name before each message id. `find(token)` answers the recipient and the scope, or `null` for a token that is malformed, unknown, revoked or past its expiry: it looks the token up by its SHA-256 and compares in constant time. `revoke(recipient)` ends every token of the recipient, in every scope, and answers how many it ended. Each revoked row stays, marked with the time of the revoke, so `issue` refuses a revoked token's nonce for every recipient, scope and expiry: a revoked link never works again and never comes to name someone else. A message that needs a working token after a revoke takes a new nonce, and the token it gets works. A token has no expiry unless `issue`'s options set `expiresAt`, in epoch milliseconds.
|
|
131
131
|
|
|
132
|
-
`suppressionStoreDdl(prefix)` makes the table `<prefix>_suppressions` (`scope`, `address_hash`, `reason`, `suppressed_on`), `recipientCapabilityDdl(prefix)` makes `<prefix>_capabilities` (`token_hash`, `recipient`, `scope`, `expires_at`, `revoked_at`) with its index on the recipient, and `emailPreferencesDdl(prefix)` makes both. The prefix is a lower-case letter and at most 39 lower-case letters, digits or underscores, other than `sqlite` and
|
|
132
|
+
`suppressionStoreDdl(prefix)` makes the table `<prefix>_suppressions` (`scope`, `address_hash`, `reason`, `suppressed_on`), `recipientCapabilityDdl(prefix)` makes `<prefix>_capabilities` (`token_hash`, `recipient`, `scope`, `expires_at`, `revoked_at`) with its index on the recipient, and `emailPreferencesDdl(prefix)` makes both. The prefix is a lower-case letter and at most 39 lower-case letters, digits or underscores; any other value throws a `RangeError` from the DDL functions and both constructors. The prefix keeps two hosts' tables apart on one database. On SQLite, take a prefix other than `sqlite` and those beginning `sqlite_`: their tables would begin `sqlite_`, names SQLite keeps for its own tables, so its `CREATE TABLE` refuses them ([CREATE TABLE](https://www.sqlite.org/lang_createtable.html)) and every call of a store on such a prefix then fails on the missing table. PostgreSQL makes them. The statements are `CREATE ... IF NOT EXISTS`, so a migration can run them again, and they run on SQLite and PostgreSQL, from one process at a time: PostgreSQL does not promise that two `CREATE TABLE IF NOT EXISTS` of one new table at once both succeed, and one of them can fail on a duplicate key in `pg_type` (pgsql-hackers, 9 October 2018: [the report](https://www.postgresql.org/message-id/93336f06-86c2-71b6-abe3-0d42cc614b2b%40postgrespro.ru) and [the reply](https://www.postgresql.org/message-id/22700.1539093909%40sss.pgh.pa.us)). A host whose processes start together runs them in a migration, such as the adapter's `runSchemaMigrations` (0.7.2 or later), which takes an advisory lock on PostgreSQL. A token's row refuses its nonce for as long as it stays, revoked or not. A host that deletes a recipient's rows, by its own foreign key with `ON DELETE CASCADE` or by hand, never issues their nonces again: each message's nonce is one no other message ever had, such as a random id or a row id that is never reused. A host that cannot promise that calls `revoke` with each deletion and keeps the rows. Each store checks every argument before its first statement. A scope, a recipient and a nonce are strings of 1 to 512 UTF-16 code units (JavaScript's `length`, in which a character past U+FFFF counts two) with no U+0000, which PostgreSQL's text refuses, and no lone surrogate, which UTF-8 cannot encode, so a driver would store something else in its place: any other value throws a `TypeError` that holds none of it, and a name is stored as given on every backend. A database error passes through as it was thrown. A store made on the handle that the adapter's `transaction` gives its callback writes inside that transaction. On a server, open the database as the example does, with the adapter's `strict` option (0.6.15 or later): without it, a `url` that is unset opens a local SQLite database in its place, and the record a sender reads before each send would live there.
|
|
133
133
|
|
|
134
134
|
## Errors and retries
|
|
135
135
|
|
|
@@ -31,9 +31,9 @@ export interface RecipientCapability {
|
|
|
31
31
|
* @param recipient The recipient: 1 to 512 UTF-16 code units, with no U+0000 and no lone surrogate.
|
|
32
32
|
* @param scope The scope, under the same rule.
|
|
33
33
|
* @param nonce The nonce, under the same rule: one message, one nonce, never another message's.
|
|
34
|
-
* @param key The key, 32 random bytes or more
|
|
35
|
-
* token from the same nonce, whatever their prefixes, so each host
|
|
36
|
-
* uses.
|
|
34
|
+
* @param key The key, 32 random bytes or more as a `Uint8Array` or a `Buffer` of any realm, kept secret by the
|
|
35
|
+
* caller. Two hosts that share a key draw the same token from the same nonce, whatever their prefixes, so each host
|
|
36
|
+
* takes a key of its own or nonces no other host uses.
|
|
37
37
|
* @param options The token's expiry, if it has one.
|
|
38
38
|
* @returns The token.
|
|
39
39
|
*/
|
|
@@ -78,8 +78,8 @@ export declare function capabilityTokenHash(token: string): string;
|
|
|
78
78
|
* with `ON DELETE CASCADE` or by hand, never issues their nonces again, and a host that cannot promise that calls
|
|
79
79
|
* `revoke` with each deletion and keeps the rows.
|
|
80
80
|
*
|
|
81
|
-
* @param prefix A lower-case letter followed by at most 39 lower-case letters, digits or underscores,
|
|
82
|
-
* `sqlite` and
|
|
81
|
+
* @param prefix A lower-case letter followed by at most 39 lower-case letters, digits or underscores. On SQLite, a
|
|
82
|
+
* prefix other than `sqlite` and those beginning `sqlite_`, whose tables SQLite keeps for itself and refuses to create.
|
|
83
83
|
* @throws RangeError when the prefix does not fit.
|
|
84
84
|
*/
|
|
85
85
|
export declare function recipientCapabilityDdl(prefix: string): string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecipientCapability.d.ts","sourceRoot":"","sources":["../../src/preferences/RecipientCapability.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"RecipientCapability.d.ts","sourceRoot":"","sources":["../../src/preferences/RecipientCapability.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAGxD,oFAAoF;AACpF,MAAM,WAAW,cAAc;IAC7B,4CAA4C;IAC5C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,2CAA2C;IAC3C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAED,kCAAkC;AAClC,MAAM,WAAW,+BAA+B;IAC9C,gHAAgH;IAChH,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;CAC7B;AAED,iDAAiD;AACjD,MAAM,WAAW,6BAA6B;IAC5C,qHAAqH;IACrH,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,MAAM,CAAC;CAC7B;AAED;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC;;;;;;;;;;;;;;;OAeG;IACH,KAAK,CACH,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,UAAU,EACf,OAAO,CAAC,EAAE,+BAA+B,GACxC,OAAO,CAAC,MAAM,CAAC,CAAC;IAEnB;;;;;;OAMG;IACH,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAAC;IAEpD;;;;;;;OAOG;IACH,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;CAC5C;AAQD;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,UAAU,GAAG,MAAM,CAEtE;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEzD;AA2DD;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAY7D;AAED;;;GAGG;AACH,qBAAa,sBAAuB,YAAW,mBAAmB;IAW9D,OAAO,CAAC,QAAQ,CAAC,EAAE;IAVrB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAS;IAC/B,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAe;IAEnC;;;;;OAKG;gBAEgB,EAAE,EAAE,kBAAkB,EACvC,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,6BAAkC;IAMvC,KAAK,CACT,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,UAAU,EACf,OAAO,CAAC,EAAE,+BAA+B,GACxC,OAAO,CAAC,MAAM,CAAC;IA4BZ,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC;IAgBnD,MAAM,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CASjD"}
|
|
@@ -13,6 +13,7 @@ exports.recipientCapabilityDdl = recipientCapabilityDdl;
|
|
|
13
13
|
* again: a revoked link never works again, and never comes to name another recipient or scope.
|
|
14
14
|
*/
|
|
15
15
|
const node_crypto_1 = require("node:crypto");
|
|
16
|
+
const node_util_1 = require("node:util");
|
|
16
17
|
const database_js_1 = require("./database.js");
|
|
17
18
|
/** A token: 43 base64url characters, 32 bytes without padding. */
|
|
18
19
|
const TOKEN = /^[A-Za-z0-9_-]{43}$/;
|
|
@@ -35,9 +36,13 @@ function capabilityToken(nonce, key) {
|
|
|
35
36
|
function capabilityTokenHash(token) {
|
|
36
37
|
return (0, node_crypto_1.createHash)('sha256').update(token, 'utf8').digest('hex');
|
|
37
38
|
}
|
|
38
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Returns the key when it is a `Uint8Array`, such as a `Buffer`, of 32 bytes or more, and otherwise throws a `TypeError`
|
|
41
|
+
* that never holds the key. The check reads the value, not its prototype, so a key made in another realm, such as a vm
|
|
42
|
+
* context or a test runner's sandbox, passes as the same bytes made here: `instanceof Uint8Array` is false for it.
|
|
43
|
+
*/
|
|
39
44
|
function requireKey(key) {
|
|
40
|
-
if (key
|
|
45
|
+
if (node_util_1.types.isUint8Array(key) && key.byteLength >= KEY_BYTES)
|
|
41
46
|
return key;
|
|
42
47
|
throw new TypeError(`The key was refused: give it as ${KEY_BYTES} bytes or more, such as a Buffer`);
|
|
43
48
|
}
|
|
@@ -86,8 +91,8 @@ function isRevoked(row) {
|
|
|
86
91
|
* with `ON DELETE CASCADE` or by hand, never issues their nonces again, and a host that cannot promise that calls
|
|
87
92
|
* `revoke` with each deletion and keeps the rows.
|
|
88
93
|
*
|
|
89
|
-
* @param prefix A lower-case letter followed by at most 39 lower-case letters, digits or underscores,
|
|
90
|
-
* `sqlite` and
|
|
94
|
+
* @param prefix A lower-case letter followed by at most 39 lower-case letters, digits or underscores. On SQLite, a
|
|
95
|
+
* prefix other than `sqlite` and those beginning `sqlite_`, whose tables SQLite keeps for itself and refuses to create.
|
|
91
96
|
* @throws RangeError when the prefix does not fit.
|
|
92
97
|
*/
|
|
93
98
|
function recipientCapabilityDdl(prefix) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RecipientCapability.js","sourceRoot":"","sources":["../../src/preferences/RecipientCapability.ts"],"names":[],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"RecipientCapability.js","sourceRoot":"","sources":["../../src/preferences/RecipientCapability.ts"],"names":[],"mappings":"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"}
|
|
@@ -54,8 +54,8 @@ export interface SuppressionStore {
|
|
|
54
54
|
* PostgreSQL, from one process at a time: PostgreSQL does not promise that two processes making one new table at once
|
|
55
55
|
* both succeed.
|
|
56
56
|
*
|
|
57
|
-
* @param prefix A lower-case letter followed by at most 39 lower-case letters, digits or underscores,
|
|
58
|
-
* `sqlite` and
|
|
57
|
+
* @param prefix A lower-case letter followed by at most 39 lower-case letters, digits or underscores. On SQLite, a
|
|
58
|
+
* prefix other than `sqlite` and those beginning `sqlite_`, whose tables SQLite keeps for itself and refuses to create.
|
|
59
59
|
* @throws RangeError when the prefix does not fit.
|
|
60
60
|
*/
|
|
61
61
|
export declare function suppressionStoreDdl(prefix: string): string;
|
|
@@ -51,8 +51,8 @@ function requireDay(value) {
|
|
|
51
51
|
* PostgreSQL, from one process at a time: PostgreSQL does not promise that two processes making one new table at once
|
|
52
52
|
* both succeed.
|
|
53
53
|
*
|
|
54
|
-
* @param prefix A lower-case letter followed by at most 39 lower-case letters, digits or underscores,
|
|
55
|
-
* `sqlite` and
|
|
54
|
+
* @param prefix A lower-case letter followed by at most 39 lower-case letters, digits or underscores. On SQLite, a
|
|
55
|
+
* prefix other than `sqlite` and those beginning `sqlite_`, whose tables SQLite keeps for itself and refuses to create.
|
|
56
56
|
* @throws RangeError when the prefix does not fit.
|
|
57
57
|
*/
|
|
58
58
|
function suppressionStoreDdl(prefix) {
|
|
@@ -32,8 +32,10 @@ export declare function changesOf(result: {
|
|
|
32
32
|
} | null | undefined): number;
|
|
33
33
|
/**
|
|
34
34
|
* Returns the prefix when it is a lower-case letter followed by at most 39 lower-case letters, digits or underscores,
|
|
35
|
-
*
|
|
36
|
-
*
|
|
35
|
+
* and otherwise throws a `RangeError`. The prefix is written into the SQL as it is, so nothing else passes. Every
|
|
36
|
+
* prefix that passes makes tables on PostgreSQL. On SQLite, `sqlite` and a prefix beginning `sqlite_` make names that
|
|
37
|
+
* SQLite keeps for its own tables, and its `CREATE TABLE` refuses them (https://www.sqlite.org/lang_createtable.html),
|
|
38
|
+
* so a host there takes another prefix.
|
|
37
39
|
*
|
|
38
40
|
* @param prefix The table prefix the caller gave.
|
|
39
41
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/preferences/database.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpE,6FAA6F;IAC7F,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CACxE;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAG3F;
|
|
1
|
+
{"version":3,"file":"database.d.ts","sourceRoot":"","sources":["../../src/preferences/database.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH;;;;;GAKG;AACH,MAAM,WAAW,kBAAkB;IACjC;;;;OAIG;IACH,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACpE,6FAA6F;IAC7F,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,GAAG,SAAS,CAAC,CAAC;CACxE;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE;IAAE,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,GAAG,SAAS,GAAG,MAAM,CAG3F;AAQD;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,OAAO,GAAG,MAAM,CAKrD;AAUD;;;;;;;GAOG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,GAAG,MAAM,CAKjE"}
|
|
@@ -18,25 +18,24 @@ function changesOf(result) {
|
|
|
18
18
|
const changes = result?.changes;
|
|
19
19
|
return typeof changes === 'number' && Number.isSafeInteger(changes) && changes >= 0 ? changes : 0;
|
|
20
20
|
}
|
|
21
|
-
/** A table prefix: a lower-case letter, then at most 39 lower-case letters, digits or underscores. */
|
|
22
|
-
const PREFIX = /^[a-z][a-z0-9_]{0,39}$/;
|
|
23
21
|
/**
|
|
24
|
-
* A prefix
|
|
25
|
-
*
|
|
22
|
+
* A table prefix: a lower-case letter, then at most 39 lower-case letters, digits or underscores, the rule of AgentOS's
|
|
23
|
+
* `durableInboxDdl`.
|
|
26
24
|
*/
|
|
27
|
-
const
|
|
25
|
+
const PREFIX = /^[a-z][a-z0-9_]{0,39}$/;
|
|
28
26
|
/**
|
|
29
27
|
* Returns the prefix when it is a lower-case letter followed by at most 39 lower-case letters, digits or underscores,
|
|
30
|
-
*
|
|
31
|
-
*
|
|
28
|
+
* and otherwise throws a `RangeError`. The prefix is written into the SQL as it is, so nothing else passes. Every
|
|
29
|
+
* prefix that passes makes tables on PostgreSQL. On SQLite, `sqlite` and a prefix beginning `sqlite_` make names that
|
|
30
|
+
* SQLite keeps for its own tables, and its `CREATE TABLE` refuses them (https://www.sqlite.org/lang_createtable.html),
|
|
31
|
+
* so a host there takes another prefix.
|
|
32
32
|
*
|
|
33
33
|
* @param prefix The table prefix the caller gave.
|
|
34
34
|
*/
|
|
35
35
|
function requirePrefix(prefix) {
|
|
36
|
-
if (typeof prefix === 'string' && PREFIX.test(prefix)
|
|
36
|
+
if (typeof prefix === 'string' && PREFIX.test(prefix))
|
|
37
37
|
return prefix;
|
|
38
|
-
throw new RangeError('The table prefix was refused: give a lower-case letter followed by at most 39 lower-case letters, digits or '
|
|
39
|
-
'underscores, other than sqlite and a prefix beginning sqlite_, whose tables SQLite keeps for itself');
|
|
38
|
+
throw new RangeError('The table prefix was refused: give a lower-case letter followed by at most 39 lower-case letters, digits or underscores');
|
|
40
39
|
}
|
|
41
40
|
/**
|
|
42
41
|
* What a text column cannot hold as given: U+0000, which PostgreSQL's text refuses, and a lone surrogate, which UTF-8
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/preferences/database.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AA0BH,8BAGC;
|
|
1
|
+
{"version":3,"file":"database.js","sourceRoot":"","sources":["../../src/preferences/database.ts"],"names":[],"mappings":";AAAA;;;GAGG;;AA0BH,8BAGC;AAiBD,sCAKC;AAkBD,kCAKC;AAvDD;;;;;;GAMG;AACH,SAAgB,SAAS,CAAC,MAAyD;IACjF,MAAM,OAAO,GAAG,MAAM,EAAE,OAAO,CAAC;IAChC,OAAO,OAAO,OAAO,KAAK,QAAQ,IAAI,MAAM,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;AACpG,CAAC;AAED;;;GAGG;AACH,MAAM,MAAM,GAAG,wBAAwB,CAAC;AAExC;;;;;;;;GAQG;AACH,SAAgB,aAAa,CAAC,MAAe;IAC3C,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,CAAC;QAAE,OAAO,MAAM,CAAC;IACrE,MAAM,IAAI,UAAU,CAClB,yHAAyH,CAC1H,CAAC;AACJ,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,GAAG,yBAAyB,CAAC;AAE7C;;;;;;;GAOG;AACH,SAAgB,WAAW,CAAC,KAAa,EAAE,KAAc;IACvD,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,MAAM,IAAI,GAAG,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,KAAK,CAAC;IAClH,MAAM,IAAI,SAAS,CACjB,OAAO,KAAK,sGAAsG,CACnH,CAAC;AACJ,CAAC"}
|
|
@@ -8,8 +8,8 @@ export type { RecipientCapability, RecipientCapabilityIssueOptions, RecipientGra
|
|
|
8
8
|
* migration makes both. A host whose tables come from different migrations runs `suppressionStoreDdl` and
|
|
9
9
|
* `recipientCapabilityDdl` where each belongs.
|
|
10
10
|
*
|
|
11
|
-
* @param prefix A lower-case letter followed by at most 39 lower-case letters, digits or underscores,
|
|
12
|
-
* `sqlite` and
|
|
11
|
+
* @param prefix A lower-case letter followed by at most 39 lower-case letters, digits or underscores. On SQLite, a
|
|
12
|
+
* prefix other than `sqlite` and those beginning `sqlite_`, whose tables SQLite keeps for itself and refuses to create.
|
|
13
13
|
* @throws RangeError when the prefix does not fit.
|
|
14
14
|
*/
|
|
15
15
|
export declare function emailPreferencesDdl(prefix: string): string;
|
|
@@ -21,8 +21,8 @@ Object.defineProperty(exports, "recipientCapabilityDdl", { enumerable: true, get
|
|
|
21
21
|
* migration makes both. A host whose tables come from different migrations runs `suppressionStoreDdl` and
|
|
22
22
|
* `recipientCapabilityDdl` where each belongs.
|
|
23
23
|
*
|
|
24
|
-
* @param prefix A lower-case letter followed by at most 39 lower-case letters, digits or underscores,
|
|
25
|
-
* `sqlite` and
|
|
24
|
+
* @param prefix A lower-case letter followed by at most 39 lower-case letters, digits or underscores. On SQLite, a
|
|
25
|
+
* prefix other than `sqlite` and those beginning `sqlite_`, whose tables SQLite keeps for itself and refuses to create.
|
|
26
26
|
* @throws RangeError when the prefix does not fit.
|
|
27
27
|
*/
|
|
28
28
|
function emailPreferencesDdl(prefix) {
|
package/manifest.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "https://agentos.sh/schemas/extension-manifest-v1.json",
|
|
3
3
|
"id": "com.framers.channels.email",
|
|
4
4
|
"name": "Email Channel Extension",
|
|
5
|
-
"version": "0.4.
|
|
5
|
+
"version": "0.4.4",
|
|
6
6
|
"description": "Bidirectional email integration: send through SMTP or Resend's HTTPS API; read, search, extract verification codes and reply through IMAP; keep a suppression record and unsubscribe tokens on a SQL database.",
|
|
7
7
|
"categories": ["channel", "communication"],
|
|
8
8
|
"platforms": ["email"],
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@framers/agentos-ext-channel-email",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "Email channel adapter for AgentOS: send through SMTP or Resend's HTTPS API; read, search, extract codes and reply through IMAP; keep a suppression record and unsubscribe tokens on a SQL database.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|