agentic-notify 0.1.0 → 0.2.1
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 +16 -3
- package/dist/{chunk-6CKFBZOF.js → chunk-3N77SYRA.js} +8 -1
- package/dist/{chunk-FMU2ED2H.js → chunk-ARL7FN6Y.js} +52 -235
- package/dist/chunk-PHY45IVQ.js +101 -0
- package/dist/cli.js +3 -3
- package/dist/{d1-CUcNko0m.d.ts → dispatch-xe2IAqWe.d.ts} +5 -34
- package/dist/{gdpr-9I4eV86m.d.ts → gdpr-BAg1U1w6.d.ts} +1 -1
- package/dist/index.d.ts +10 -7
- package/dist/index.js +10 -10
- package/dist/mcp-bin.js +23 -15
- package/dist/mcp.d.ts +2 -2
- package/dist/mcp.js +82 -7
- package/dist/sdk.d.ts +8 -8
- package/dist/{email-UqtllxjF.d.ts → testing-46doJP5Y.d.ts} +3 -2
- package/dist/testing.d.ts +4 -12
- package/dist/testing.js +2 -147
- package/dist/types-BdX7UTFu.d.ts +20 -0
- package/dist/{types-GuAgyMOc.d.ts → types-anA8DMQH.d.ts} +31 -31
- package/dist/worker.d.ts +10 -6
- package/dist/worker.js +17 -7
- package/package.json +5 -2
- package/dist/chunk-DEPJ3SHO.js +0 -86
- package/dist/chunk-GJFAXI2K.js +0 -104
- package/dist/types-Dkst26cb.d.ts +0 -69
package/README.md
CHANGED
|
@@ -5,7 +5,18 @@
|
|
|
5
5
|
|
|
6
6
|
Notifications for humans and agents: in-app inbox, email and webhook delivery, with a CLI, API, MCP server and SDK.
|
|
7
7
|
|
|
8
|
-
**Status: 0.1.0,
|
|
8
|
+
**Status: 0.2.1.** Breaking for 0.1.0 code and databases, see the CHANGELOG upgrade steps. Works today: the notification model, preferences and mute, digest, a store port from agentic-identity with its memory and D1 adapters, signed webhook and email delivery through a dispatcher, an HTTP API in a Worker, an SDK, a CLI, MCP tools, per-recipient export and erase, and a retention sweep. Not done: a real email provider adapter, webhook endpoint management, and a web UI.
|
|
9
|
+
|
|
10
|
+
## Live demo
|
|
11
|
+
|
|
12
|
+
A demo Worker runs at https://agentic-notify-demo.hello-fd1.workers.dev. `GET /health` answers 200. The `/api/v1` routes need a bearer token, so without one they answer 401. The demo keeps its data in the EU and sweeps notifications after 7 days.
|
|
13
|
+
|
|
14
|
+
Redeploy your own copy with `wrangler.demo.jsonc` (create the D1 database with `--location weur`, put its id in the file, set the `NOTIFY_TOKENS` secret):
|
|
15
|
+
|
|
16
|
+
```sh
|
|
17
|
+
npx wrangler secret put NOTIFY_TOKENS --config wrangler.demo.jsonc
|
|
18
|
+
WRANGLER_SEND_METRICS=false npm run deploy:demo
|
|
19
|
+
```
|
|
9
20
|
|
|
10
21
|
## Install
|
|
11
22
|
|
|
@@ -18,9 +29,9 @@ npm install agentic-notify
|
|
|
18
29
|
Library, with the memory store:
|
|
19
30
|
|
|
20
31
|
```ts
|
|
21
|
-
import { createNotifier,
|
|
32
|
+
import { createNotifier, createMemoryStore } from "agentic-notify";
|
|
22
33
|
|
|
23
|
-
const notifier = createNotifier({ store:
|
|
34
|
+
const notifier = createNotifier({ store: createMemoryStore() });
|
|
24
35
|
await notifier.send({
|
|
25
36
|
recipient: { kind: "agent", id: "helper" },
|
|
26
37
|
type: "approval_request",
|
|
@@ -78,6 +89,8 @@ Auth is a port: `PrincipalResolver` turns a request into `{ kind: "human" | "age
|
|
|
78
89
|
## Privacy
|
|
79
90
|
|
|
80
91
|
- Export and erase work per recipient. `retentionMs` plus `sweep()` (or a cron trigger on the Worker, with `NOTIFY_RETENTION_DAYS`) deletes old notifications.
|
|
92
|
+
- Erase and sweep delete in batches of 100 inside one transaction each, so a large erase does not cost one query per row.
|
|
93
|
+
- The Worker reads `NOTIFY_REGION` (`eu`, `us` or `unknown`, default `eu`). The D1 adapter refuses a database that does not report a serving region in that area. For local runs without a real D1, set `NOTIFY_REGION=unknown`.
|
|
81
94
|
- Logs hold ids, channel names and error names only. They never hold bodies or addresses.
|
|
82
95
|
- Erase removes a recipient's own notifications and preferences. Notifications that name the person only as actor stay with their recipients.
|
|
83
96
|
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import { createMemoryStore } from "agentic-identity";
|
|
3
|
+
|
|
1
4
|
// src/delivery/dispatch.ts
|
|
2
5
|
function createDispatcher(options) {
|
|
3
6
|
return {
|
|
@@ -148,6 +151,7 @@ function createWebhookChannel(options) {
|
|
|
148
151
|
}
|
|
149
152
|
|
|
150
153
|
// src/index.ts
|
|
154
|
+
import { createD1Store, D1_SCHEMA } from "agentic-identity/cloudflare";
|
|
151
155
|
var PACKAGE_NAME = "agentic-notify";
|
|
152
156
|
|
|
153
157
|
export {
|
|
@@ -157,5 +161,8 @@ export {
|
|
|
157
161
|
signPayload,
|
|
158
162
|
verifySignature,
|
|
159
163
|
createWebhookChannel,
|
|
160
|
-
PACKAGE_NAME
|
|
164
|
+
PACKAGE_NAME,
|
|
165
|
+
createMemoryStore,
|
|
166
|
+
createD1Store,
|
|
167
|
+
D1_SCHEMA
|
|
161
168
|
};
|
|
@@ -1,105 +1,3 @@
|
|
|
1
|
-
import {
|
|
2
|
-
canonicalJson,
|
|
3
|
-
clampLimit,
|
|
4
|
-
decodeCursor,
|
|
5
|
-
encodeCursor
|
|
6
|
-
} from "./chunk-GJFAXI2K.js";
|
|
7
|
-
|
|
8
|
-
// package.json
|
|
9
|
-
var package_default = {
|
|
10
|
-
name: "agentic-notify",
|
|
11
|
-
version: "0.1.0",
|
|
12
|
-
description: "Notifications for humans and agents: in-app inbox, webhook and email delivery, with SDK, CLI, MCP server and Worker API.",
|
|
13
|
-
keywords: [
|
|
14
|
-
"notifications",
|
|
15
|
-
"inbox",
|
|
16
|
-
"webhook",
|
|
17
|
-
"agents",
|
|
18
|
-
"mcp",
|
|
19
|
-
"cli",
|
|
20
|
-
"cloudflare-workers",
|
|
21
|
-
"typescript"
|
|
22
|
-
],
|
|
23
|
-
license: "MIT",
|
|
24
|
-
author: "Pooria Arab",
|
|
25
|
-
repository: {
|
|
26
|
-
type: "git",
|
|
27
|
-
url: "git+https://github.com/pooriaarab/agentic-notify.git"
|
|
28
|
-
},
|
|
29
|
-
type: "module",
|
|
30
|
-
private: false,
|
|
31
|
-
engines: {
|
|
32
|
-
node: ">=20"
|
|
33
|
-
},
|
|
34
|
-
files: [
|
|
35
|
-
"dist"
|
|
36
|
-
],
|
|
37
|
-
publishConfig: {
|
|
38
|
-
access: "public"
|
|
39
|
-
},
|
|
40
|
-
mcpName: "io.github.pooriaarab/agentic-notify",
|
|
41
|
-
main: "./dist/index.js",
|
|
42
|
-
types: "./dist/index.d.ts",
|
|
43
|
-
exports: {
|
|
44
|
-
".": {
|
|
45
|
-
types: "./dist/index.d.ts",
|
|
46
|
-
import: "./dist/index.js"
|
|
47
|
-
},
|
|
48
|
-
"./testing": {
|
|
49
|
-
types: "./dist/testing.d.ts",
|
|
50
|
-
import: "./dist/testing.js"
|
|
51
|
-
},
|
|
52
|
-
"./sdk": {
|
|
53
|
-
types: "./dist/sdk.d.ts",
|
|
54
|
-
import: "./dist/sdk.js"
|
|
55
|
-
},
|
|
56
|
-
"./worker": {
|
|
57
|
-
types: "./dist/worker.d.ts",
|
|
58
|
-
import: "./dist/worker.js"
|
|
59
|
-
}
|
|
60
|
-
},
|
|
61
|
-
bin: {
|
|
62
|
-
"agentic-notify": "./dist/cli.js",
|
|
63
|
-
"agentic-notify-mcp": "./dist/mcp-bin.js"
|
|
64
|
-
},
|
|
65
|
-
scripts: {
|
|
66
|
-
build: "tsup",
|
|
67
|
-
typecheck: "tsc --noEmit",
|
|
68
|
-
test: "vitest run",
|
|
69
|
-
clean: "rm -rf dist",
|
|
70
|
-
prepack: "npm run build",
|
|
71
|
-
"ci:local": "npm run build && npm run typecheck && npm run check:worker && npm test",
|
|
72
|
-
"check:worker": "esbuild src/worker.ts --bundle --platform=neutral --format=esm --log-level=warning --outfile=node_modules/.cache/worker-check.js"
|
|
73
|
-
},
|
|
74
|
-
dependencies: {
|
|
75
|
-
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
76
|
-
hono: "^4.13.14",
|
|
77
|
-
zod: "^3.25.0"
|
|
78
|
-
},
|
|
79
|
-
devDependencies: {
|
|
80
|
-
"@types/node": "^22.0.0",
|
|
81
|
-
esbuild: "^0.28.2",
|
|
82
|
-
miniflare: "^4.20260730.0",
|
|
83
|
-
tsup: "^8.0.0",
|
|
84
|
-
typescript: "^5.6.0",
|
|
85
|
-
vitest: "^3.0.0"
|
|
86
|
-
},
|
|
87
|
-
peerDependencies: {
|
|
88
|
-
vitest: ">=3"
|
|
89
|
-
},
|
|
90
|
-
peerDependenciesMeta: {
|
|
91
|
-
vitest: {
|
|
92
|
-
optional: true
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
};
|
|
96
|
-
|
|
97
|
-
// src/version.ts
|
|
98
|
-
if (typeof package_default.version !== "string" || package_default.version.length === 0) {
|
|
99
|
-
throw new Error("package.json carries no version string");
|
|
100
|
-
}
|
|
101
|
-
var VERSION = package_default.version;
|
|
102
|
-
|
|
103
1
|
// src/core/types.ts
|
|
104
2
|
import { z } from "zod";
|
|
105
3
|
var CHANNELS = ["inbox", "webhook", "email"];
|
|
@@ -142,12 +40,18 @@ function recipientKey(recipient) {
|
|
|
142
40
|
return `${recipient.kind}:${recipient.id}`;
|
|
143
41
|
}
|
|
144
42
|
|
|
145
|
-
// src/core/service.ts
|
|
146
|
-
import { z as z2 } from "zod";
|
|
147
|
-
|
|
148
43
|
// src/core/gdpr.ts
|
|
149
44
|
var NOTES = "notifications";
|
|
150
45
|
var PREFS = "preferences";
|
|
46
|
+
var DELETE_BATCH = 100;
|
|
47
|
+
async function deleteAll(store, ids) {
|
|
48
|
+
for (let i = 0; i < ids.length; i += DELETE_BATCH) {
|
|
49
|
+
const batch = ids.slice(i, i + DELETE_BATCH);
|
|
50
|
+
await store.transaction(async (tx) => {
|
|
51
|
+
for (const id2 of batch) await tx.delete(NOTES, id2);
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
151
55
|
async function* notificationDocs(store, recipient, ordered = true) {
|
|
152
56
|
let cursor;
|
|
153
57
|
do {
|
|
@@ -165,7 +69,7 @@ async function* notificationDocs(store, recipient, ordered = true) {
|
|
|
165
69
|
async function exportRecipient(store, recipient) {
|
|
166
70
|
const notifications = [];
|
|
167
71
|
for await (const item of notificationDocs(store, recipient, false)) {
|
|
168
|
-
const { recipientKey: _drop, ...rest } = item.
|
|
72
|
+
const { recipientKey: _drop, ...rest } = item.data;
|
|
169
73
|
notifications.push(notificationSchema.parse(rest));
|
|
170
74
|
}
|
|
171
75
|
notifications.sort((a, b) => a.createdAt - b.createdAt || (a.id < b.id ? -1 : 1));
|
|
@@ -179,7 +83,7 @@ async function eraseRecipient(store, recipient) {
|
|
|
179
83
|
const ids = [];
|
|
180
84
|
for await (const item of notificationDocs(store, recipient, false)) ids.push(item.id);
|
|
181
85
|
if (ids.length === 0) break;
|
|
182
|
-
|
|
86
|
+
await deleteAll(store, ids);
|
|
183
87
|
removed += ids.length;
|
|
184
88
|
}
|
|
185
89
|
await store.delete(PREFS, recipientKey(recipient));
|
|
@@ -192,20 +96,19 @@ async function sweepBefore(store, cutoff) {
|
|
|
192
96
|
const gone = /* @__PURE__ */ new Set();
|
|
193
97
|
for (; ; ) {
|
|
194
98
|
const page = await store.query(NOTES, { orderBy: { field: "createdAt", direction: "asc" }, limit });
|
|
195
|
-
const old = page.items.filter((i) => typeof i.
|
|
99
|
+
const old = page.items.filter((i) => typeof i.data.createdAt === "number" && i.data.createdAt < cutoff);
|
|
196
100
|
if (old.some((i) => gone.has(i.id))) throw new Error("retention sweep did not finish: deletes had no effect");
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
gone.add(item.id);
|
|
200
|
-
}
|
|
101
|
+
await deleteAll(store, old.map((i) => i.id));
|
|
102
|
+
for (const item of old) gone.add(item.id);
|
|
201
103
|
removed += old.length;
|
|
202
104
|
if (old.length > 0) continue;
|
|
203
|
-
if (page.items.length < limit || page.items.some((i) => typeof i.
|
|
105
|
+
if (page.items.length < limit || page.items.some((i) => typeof i.data.createdAt === "number")) return removed;
|
|
204
106
|
throw new Error("retention sweep blocked: a full page of notifications has no usable createdAt");
|
|
205
107
|
}
|
|
206
108
|
}
|
|
207
109
|
|
|
208
110
|
// src/core/service.ts
|
|
111
|
+
import { z as z2 } from "zod";
|
|
209
112
|
var NotFoundError = class extends Error {
|
|
210
113
|
code = "NOT_FOUND";
|
|
211
114
|
constructor(message = "notification not found") {
|
|
@@ -220,6 +123,22 @@ function fromDoc(doc) {
|
|
|
220
123
|
const { recipientKey: _drop, ...rest } = doc;
|
|
221
124
|
return notificationSchema.parse(rest);
|
|
222
125
|
}
|
|
126
|
+
var DEFAULT_LIMIT = 50;
|
|
127
|
+
var MAX_LIMIT = 200;
|
|
128
|
+
function clampLimit(limit) {
|
|
129
|
+
if (limit === void 0) return DEFAULT_LIMIT;
|
|
130
|
+
if (!Number.isInteger(limit) || limit < 1) throw new RangeError("limit must be an integer of 1 or more");
|
|
131
|
+
return Math.min(limit, MAX_LIMIT);
|
|
132
|
+
}
|
|
133
|
+
function checkCursor(cursor) {
|
|
134
|
+
try {
|
|
135
|
+
const bytes = Uint8Array.from(atob(cursor.replace(/-/g, "+").replace(/_/g, "/")), (c) => c.charCodeAt(0));
|
|
136
|
+
const parsed = JSON.parse(new TextDecoder().decode(bytes));
|
|
137
|
+
if (Array.isArray(parsed) && parsed.length === 2 && Number.isFinite(parsed[0]) && typeof parsed[1] === "string") return;
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
throw new RangeError("bad cursor");
|
|
141
|
+
}
|
|
223
142
|
function createNotifier(options) {
|
|
224
143
|
const { store } = options;
|
|
225
144
|
const now = options.now ?? (() => Date.now());
|
|
@@ -253,41 +172,43 @@ function createNotifier(options) {
|
|
|
253
172
|
readAt: null
|
|
254
173
|
});
|
|
255
174
|
if (!channels.includes("inbox")) return { notification, stored: false, channels, muted: false };
|
|
256
|
-
|
|
257
|
-
throw new Error("notification id collision");
|
|
258
|
-
|
|
175
|
+
await store.transaction(async (tx) => {
|
|
176
|
+
if (await tx.get(NOTES, notification.id)) throw new Error("notification id collision");
|
|
177
|
+
await tx.put(NOTES, notification.id, toDoc(notification));
|
|
178
|
+
});
|
|
259
179
|
return { notification, stored: true, channels, muted: false };
|
|
260
180
|
}
|
|
261
181
|
async function list(recipient, opts = {}) {
|
|
262
182
|
const r = recipientSchema.parse(recipient);
|
|
263
183
|
const where = { recipientKey: recipientKey(r) };
|
|
264
184
|
if (opts.unreadOnly) where.readAt = null;
|
|
185
|
+
if (opts.cursor !== void 0) checkCursor(opts.cursor);
|
|
186
|
+
const limit = clampLimit(opts.limit);
|
|
265
187
|
const page = await store.query(NOTES, {
|
|
266
188
|
where,
|
|
267
189
|
orderBy: { field: "createdAt", direction: "desc" },
|
|
268
|
-
limit
|
|
190
|
+
limit,
|
|
269
191
|
cursor: opts.cursor
|
|
270
192
|
});
|
|
271
|
-
return { items: page.items.map((i) => fromDoc(i.
|
|
193
|
+
return { items: page.items.map((i) => fromDoc(i.data)), nextCursor: page.nextCursor ?? null };
|
|
272
194
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
const doc = await store.get(NOTES, id2);
|
|
276
|
-
if (!doc || doc.recipientKey !== recipientKey(r)) throw new NotFoundError();
|
|
195
|
+
function check(doc, recipient) {
|
|
196
|
+
if (!doc || doc.recipientKey !== recipientKey(recipient)) throw new NotFoundError();
|
|
277
197
|
return doc;
|
|
278
198
|
}
|
|
279
199
|
async function get(recipient, id2) {
|
|
280
|
-
|
|
200
|
+
const r = recipientSchema.parse(recipient);
|
|
201
|
+
return fromDoc(check(await store.get(NOTES, id2), r));
|
|
281
202
|
}
|
|
282
203
|
async function markReadOnce(recipient, id2) {
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
const n = fromDoc(
|
|
204
|
+
const r = recipientSchema.parse(recipient);
|
|
205
|
+
return store.transaction(async (tx) => {
|
|
206
|
+
const n = fromDoc(check(await tx.get(NOTES, id2), r));
|
|
286
207
|
if (n.readAt !== null) return { notification: n, won: false };
|
|
287
208
|
const next = notificationSchema.parse({ ...n, readAt: now() });
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
209
|
+
await tx.put(NOTES, id2, toDoc(next));
|
|
210
|
+
return { notification: next, won: true };
|
|
211
|
+
});
|
|
291
212
|
}
|
|
292
213
|
async function markRead(recipient, id2) {
|
|
293
214
|
return (await markReadOnce(recipient, id2)).notification;
|
|
@@ -366,110 +287,7 @@ function createNotifier(options) {
|
|
|
366
287
|
};
|
|
367
288
|
}
|
|
368
289
|
|
|
369
|
-
// src/adapters/d1.ts
|
|
370
|
-
var FIELD = /^[A-Za-z_][A-Za-z0-9_]{0,63}$/;
|
|
371
|
-
var SCHEMA = `CREATE TABLE IF NOT EXISTS notify_docs (
|
|
372
|
-
collection TEXT NOT NULL,
|
|
373
|
-
id TEXT NOT NULL,
|
|
374
|
-
doc TEXT NOT NULL,
|
|
375
|
-
PRIMARY KEY (collection, id)
|
|
376
|
-
)`;
|
|
377
|
-
function field(name) {
|
|
378
|
-
if (!FIELD.test(name)) throw new RangeError("bad field name");
|
|
379
|
-
return `json_extract(doc, '$.${name}')`;
|
|
380
|
-
}
|
|
381
|
-
function bindable(v) {
|
|
382
|
-
return typeof v === "boolean" ? v ? 1 : 0 : v;
|
|
383
|
-
}
|
|
384
|
-
var TYPES = {
|
|
385
|
-
number: ["integer", "real"],
|
|
386
|
-
string: ["text"],
|
|
387
|
-
boolean: ["true", "false"]
|
|
388
|
-
};
|
|
389
|
-
var INDEXES = [
|
|
390
|
-
`CREATE INDEX IF NOT EXISTS notify_by_recipient ON notify_docs (collection, json_extract(doc, '$.recipientKey'), json_extract(doc, '$.createdAt'), id)`,
|
|
391
|
-
`CREATE INDEX IF NOT EXISTS notify_by_created ON notify_docs (collection, json_extract(doc, '$.createdAt'), id)`
|
|
392
|
-
];
|
|
393
|
-
async function migrateD1(db) {
|
|
394
|
-
for (const sql of [SCHEMA, ...INDEXES]) await db.prepare(sql).bind().run();
|
|
395
|
-
}
|
|
396
|
-
var D1Store = class {
|
|
397
|
-
constructor(db) {
|
|
398
|
-
this.db = db;
|
|
399
|
-
}
|
|
400
|
-
db;
|
|
401
|
-
async get(collection, id2) {
|
|
402
|
-
const row = await this.db.prepare("SELECT doc FROM notify_docs WHERE collection = ? AND id = ?").bind(collection, id2).first();
|
|
403
|
-
return row ? JSON.parse(row.doc) : null;
|
|
404
|
-
}
|
|
405
|
-
async put(collection, id2, doc) {
|
|
406
|
-
await this.db.prepare("INSERT OR REPLACE INTO notify_docs (collection, id, doc) VALUES (?, ?, ?)").bind(collection, id2, canonicalJson(doc)).run();
|
|
407
|
-
}
|
|
408
|
-
async create(collection, id2, doc) {
|
|
409
|
-
const res = await this.db.prepare("INSERT OR IGNORE INTO notify_docs (collection, id, doc) VALUES (?, ?, ?)").bind(collection, id2, canonicalJson(doc)).run();
|
|
410
|
-
return (res.meta.changes ?? 0) > 0;
|
|
411
|
-
}
|
|
412
|
-
async replaceIf(collection, id2, expected, next) {
|
|
413
|
-
const res = await this.db.prepare("UPDATE notify_docs SET doc = ? WHERE collection = ? AND id = ? AND doc = ?").bind(canonicalJson(next), collection, id2, canonicalJson(expected)).run();
|
|
414
|
-
return (res.meta.changes ?? 0) > 0;
|
|
415
|
-
}
|
|
416
|
-
async delete(collection, id2) {
|
|
417
|
-
await this.db.prepare("DELETE FROM notify_docs WHERE collection = ? AND id = ?").bind(collection, id2).run();
|
|
418
|
-
}
|
|
419
|
-
async query(collection, query = {}) {
|
|
420
|
-
const limit = clampLimit(query.limit);
|
|
421
|
-
const desc = query.orderBy?.direction === "desc";
|
|
422
|
-
const key = query.orderBy ? field(query.orderBy.field) : "id";
|
|
423
|
-
const conds = ["collection = ?"];
|
|
424
|
-
const args = [collection];
|
|
425
|
-
for (const [name, value] of Object.entries(query.where ?? {})) {
|
|
426
|
-
if (value === null) conds.push(`${field(name)} IS NULL`);
|
|
427
|
-
else {
|
|
428
|
-
const types = TYPES[typeof value] ?? [];
|
|
429
|
-
conds.push(`${field(name)} = ? AND json_type(doc, '$.${name}') IN (${types.map(() => "?").join(",")})`);
|
|
430
|
-
args.push(bindable(value), ...types);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
if (query.cursor !== void 0) {
|
|
434
|
-
const [cv, cid] = decodeCursor(query.cursor);
|
|
435
|
-
if (query.orderBy && cv !== null) {
|
|
436
|
-
const allowed = TYPES[typeof cv] ?? [];
|
|
437
|
-
const marks = allowed.map(() => "?").join(",");
|
|
438
|
-
const bad = await this.db.prepare(
|
|
439
|
-
`SELECT 1 AS x FROM notify_docs WHERE ${conds.join(" AND ")} AND ${key} IS NOT NULL AND json_type(doc, '$.${query.orderBy.field}') NOT IN (${marks}) LIMIT 1`
|
|
440
|
-
).bind(...args, ...allowed).first();
|
|
441
|
-
if (bad) throw new RangeError("bad cursor");
|
|
442
|
-
}
|
|
443
|
-
const idCmp = desc ? "<" : ">";
|
|
444
|
-
if (!query.orderBy) {
|
|
445
|
-
conds.push(`id ${idCmp} ?`);
|
|
446
|
-
args.push(cid);
|
|
447
|
-
} else if (cv === null) {
|
|
448
|
-
conds.push(desc ? `(${key} IS NULL AND id < ?)` : `(${key} IS NOT NULL OR id > ?)`);
|
|
449
|
-
args.push(cid);
|
|
450
|
-
} else if (desc) {
|
|
451
|
-
conds.push(`(${key} < ? OR ${key} IS NULL OR (${key} = ? AND id < ?))`);
|
|
452
|
-
args.push(bindable(cv), bindable(cv), cid);
|
|
453
|
-
} else {
|
|
454
|
-
conds.push(`(${key} > ? OR (${key} = ? AND id > ?))`);
|
|
455
|
-
args.push(bindable(cv), bindable(cv), cid);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
const dir = desc ? "DESC" : "ASC";
|
|
459
|
-
const sql = `SELECT id, doc FROM notify_docs WHERE ${conds.join(" AND ")} ORDER BY ${key} ${dir}, id ${dir} LIMIT ?`;
|
|
460
|
-
const { results } = await this.db.prepare(sql).bind(...args, limit + 1).all();
|
|
461
|
-
const rows = results.slice(0, limit).map((r) => ({ id: r.id, doc: JSON.parse(r.doc) }));
|
|
462
|
-
const last = rows[rows.length - 1];
|
|
463
|
-
const lastKey = last && query.orderBy ? last.doc[query.orderBy.field] ?? null : last?.id ?? null;
|
|
464
|
-
return {
|
|
465
|
-
items: rows,
|
|
466
|
-
nextCursor: results.length > limit && last ? encodeCursor(lastKey, last.id) : null
|
|
467
|
-
};
|
|
468
|
-
}
|
|
469
|
-
};
|
|
470
|
-
|
|
471
290
|
export {
|
|
472
|
-
VERSION,
|
|
473
291
|
CHANNELS,
|
|
474
292
|
recipientSchema,
|
|
475
293
|
typeNameSchema,
|
|
@@ -478,8 +296,7 @@ export {
|
|
|
478
296
|
DEFAULT_CHANNELS,
|
|
479
297
|
defaultPreferences,
|
|
480
298
|
recipientKey,
|
|
299
|
+
NOTES,
|
|
481
300
|
NotFoundError,
|
|
482
|
-
createNotifier
|
|
483
|
-
migrateD1,
|
|
484
|
-
D1Store
|
|
301
|
+
createNotifier
|
|
485
302
|
};
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// package.json
|
|
2
|
+
var package_default = {
|
|
3
|
+
name: "agentic-notify",
|
|
4
|
+
version: "0.2.1",
|
|
5
|
+
description: "Notifications for humans and agents: in-app inbox, webhook and email delivery, with SDK, CLI, MCP server and Worker API.",
|
|
6
|
+
keywords: [
|
|
7
|
+
"notifications",
|
|
8
|
+
"inbox",
|
|
9
|
+
"webhook",
|
|
10
|
+
"agents",
|
|
11
|
+
"mcp",
|
|
12
|
+
"cli",
|
|
13
|
+
"cloudflare-workers",
|
|
14
|
+
"typescript"
|
|
15
|
+
],
|
|
16
|
+
license: "MIT",
|
|
17
|
+
author: "Pooria Arab",
|
|
18
|
+
repository: {
|
|
19
|
+
type: "git",
|
|
20
|
+
url: "git+https://github.com/pooriaarab/agentic-notify.git"
|
|
21
|
+
},
|
|
22
|
+
type: "module",
|
|
23
|
+
private: false,
|
|
24
|
+
engines: {
|
|
25
|
+
node: ">=20"
|
|
26
|
+
},
|
|
27
|
+
files: [
|
|
28
|
+
"dist"
|
|
29
|
+
],
|
|
30
|
+
publishConfig: {
|
|
31
|
+
access: "public"
|
|
32
|
+
},
|
|
33
|
+
mcpName: "io.github.pooriaarab/agentic-notify",
|
|
34
|
+
main: "./dist/index.js",
|
|
35
|
+
types: "./dist/index.d.ts",
|
|
36
|
+
exports: {
|
|
37
|
+
".": {
|
|
38
|
+
types: "./dist/index.d.ts",
|
|
39
|
+
import: "./dist/index.js"
|
|
40
|
+
},
|
|
41
|
+
"./testing": {
|
|
42
|
+
types: "./dist/testing.d.ts",
|
|
43
|
+
import: "./dist/testing.js"
|
|
44
|
+
},
|
|
45
|
+
"./sdk": {
|
|
46
|
+
types: "./dist/sdk.d.ts",
|
|
47
|
+
import: "./dist/sdk.js"
|
|
48
|
+
},
|
|
49
|
+
"./worker": {
|
|
50
|
+
types: "./dist/worker.d.ts",
|
|
51
|
+
import: "./dist/worker.js"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
bin: {
|
|
55
|
+
"agentic-notify": "./dist/cli.js",
|
|
56
|
+
"agentic-notify-mcp": "./dist/mcp-bin.js"
|
|
57
|
+
},
|
|
58
|
+
scripts: {
|
|
59
|
+
build: "tsup",
|
|
60
|
+
typecheck: "tsc --noEmit",
|
|
61
|
+
test: "vitest run",
|
|
62
|
+
clean: "rm -rf dist",
|
|
63
|
+
prepack: "npm run build",
|
|
64
|
+
"ci:local": "npm run build && npm run typecheck && npm run check:worker && npm test",
|
|
65
|
+
"deploy:demo": "wrangler deploy --config wrangler.demo.jsonc",
|
|
66
|
+
"check:worker": "esbuild src/worker.ts --bundle --platform=neutral --format=esm --log-level=warning --outfile=node_modules/.cache/worker-check.js"
|
|
67
|
+
},
|
|
68
|
+
dependencies: {
|
|
69
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
70
|
+
"agentic-identity": "^0.4.0",
|
|
71
|
+
hono: "^4.13.14",
|
|
72
|
+
zod: "^3.25.0"
|
|
73
|
+
},
|
|
74
|
+
devDependencies: {
|
|
75
|
+
"@types/node": "^22.0.0",
|
|
76
|
+
esbuild: "^0.28.2",
|
|
77
|
+
miniflare: "^4.20260730.0",
|
|
78
|
+
tsup: "^8.0.0",
|
|
79
|
+
typescript: "^5.6.0",
|
|
80
|
+
vitest: "^3.0.0",
|
|
81
|
+
wrangler: "^4.155.0"
|
|
82
|
+
},
|
|
83
|
+
peerDependencies: {
|
|
84
|
+
vitest: ">=3"
|
|
85
|
+
},
|
|
86
|
+
peerDependenciesMeta: {
|
|
87
|
+
vitest: {
|
|
88
|
+
optional: true
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// src/version.ts
|
|
94
|
+
if (typeof package_default.version !== "string" || package_default.version.length === 0) {
|
|
95
|
+
throw new Error("package.json carries no version string");
|
|
96
|
+
}
|
|
97
|
+
var VERSION = package_default.version;
|
|
98
|
+
|
|
99
|
+
export {
|
|
100
|
+
VERSION
|
|
101
|
+
};
|
package/dist/cli.js
CHANGED
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
PACKAGE_NAME
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-3N77SYRA.js";
|
|
5
5
|
import {
|
|
6
6
|
ApiError,
|
|
7
7
|
createClient
|
|
8
8
|
} from "./chunk-FJPEUDTG.js";
|
|
9
9
|
import "./chunk-KJKM56FT.js";
|
|
10
|
+
import "./chunk-ARL7FN6Y.js";
|
|
10
11
|
import {
|
|
11
12
|
VERSION
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-GJFAXI2K.js";
|
|
13
|
+
} from "./chunk-PHY45IVQ.js";
|
|
14
14
|
|
|
15
15
|
// src/cli.ts
|
|
16
16
|
import { realpathSync } from "fs";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import { R as RecipientExport } from './gdpr-
|
|
2
|
-
import {
|
|
3
|
-
import { S as SendInput, N as Notification,
|
|
1
|
+
import { R as RecipientExport } from './gdpr-BAg1U1w6.js';
|
|
2
|
+
import { Store } from 'agentic-identity';
|
|
3
|
+
import { S as SendInput, N as Notification, C as Channel, R as Recipient, D as Digest, P as Preferences } from './types-anA8DMQH.js';
|
|
4
|
+
import { D as DeliveryChannel, L as Logger } from './types-BdX7UTFu.js';
|
|
4
5
|
|
|
5
6
|
interface NotifierOptions {
|
|
6
7
|
store: Store;
|
|
@@ -73,34 +74,4 @@ declare function createDispatcher(options: DispatcherOptions): {
|
|
|
73
74
|
notify(input: SendInput): Promise<DispatchResult>;
|
|
74
75
|
};
|
|
75
76
|
|
|
76
|
-
|
|
77
|
-
interface D1Like {
|
|
78
|
-
prepare(sql: string): {
|
|
79
|
-
bind(...values: unknown[]): {
|
|
80
|
-
first<T = unknown>(): Promise<T | null>;
|
|
81
|
-
all<T = unknown>(): Promise<{
|
|
82
|
-
results: T[];
|
|
83
|
-
}>;
|
|
84
|
-
run(): Promise<{
|
|
85
|
-
meta: {
|
|
86
|
-
changes?: number;
|
|
87
|
-
};
|
|
88
|
-
}>;
|
|
89
|
-
};
|
|
90
|
-
};
|
|
91
|
-
}
|
|
92
|
-
/** Create the table and indexes. Safe to run on every start. */
|
|
93
|
-
declare function migrateD1(db: D1Like): Promise<void>;
|
|
94
|
-
/** Store adapter for Cloudflare D1. Run `migrateD1` once first. */
|
|
95
|
-
declare class D1Store implements Store {
|
|
96
|
-
private readonly db;
|
|
97
|
-
constructor(db: D1Like);
|
|
98
|
-
get(collection: string, id: string): Promise<Doc | null>;
|
|
99
|
-
put(collection: string, id: string, doc: Doc): Promise<void>;
|
|
100
|
-
create(collection: string, id: string, doc: Doc): Promise<boolean>;
|
|
101
|
-
replaceIf(collection: string, id: string, expected: Doc, next: Doc): Promise<boolean>;
|
|
102
|
-
delete(collection: string, id: string): Promise<void>;
|
|
103
|
-
query(collection: string, query?: Query): Promise<Page>;
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
export { type D1Like as D, type ListOptions as L, NotFoundError as N, type SendResult as S, D1Store as a, type DispatchResult as b, type DispatcherOptions as c, type ListResult as d, type Notifier as e, type NotifierOptions as f, createDispatcher as g, createNotifier as h, migrateD1 as m };
|
|
77
|
+
export { type DispatchResult as D, type ListOptions as L, NotFoundError as N, type SendResult as S, type DispatcherOptions as a, type ListResult as b, type Notifier as c, type NotifierOptions as d, createDispatcher as e, createNotifier as f };
|
package/dist/index.d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
export { D as
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
export {
|
|
7
|
-
export {
|
|
1
|
+
export { D as DispatchResult, a as DispatcherOptions, L as ListOptions, b as ListResult, N as NotFoundError, c as Notifier, d as NotifierOptions, S as SendResult, e as createDispatcher, f as createNotifier } from './dispatch-xe2IAqWe.js';
|
|
2
|
+
export { Doc, Page, Query, Scalar, Store, StoreOps, createMemoryStore } from 'agentic-identity';
|
|
3
|
+
import { R as Recipient } from './types-anA8DMQH.js';
|
|
4
|
+
export { A as Actor, b as CHANNELS, C as Channel, c as DEFAULT_CHANNELS, D as Digest, d as DigestGroup, N as Notification, P as Preferences, a as ResourceRef, S as SendInput, e as defaultPreferences, r as recipientKey } from './types-anA8DMQH.js';
|
|
5
|
+
import { D as DeliveryChannel } from './types-BdX7UTFu.js';
|
|
6
|
+
export { a as DeliveryError, L as Logger } from './types-BdX7UTFu.js';
|
|
7
|
+
export { E as EmailChannelOptions, a as EmailMessage, b as EmailPort, c as createEmailChannel } from './testing-46doJP5Y.js';
|
|
8
|
+
export { D1Like, D1_SCHEMA, createD1Store } from 'agentic-identity/cloudflare';
|
|
9
|
+
export { R as RecipientExport } from './gdpr-BAg1U1w6.js';
|
|
8
10
|
import 'zod';
|
|
11
|
+
import 'agentic-identity/testing';
|
|
9
12
|
|
|
10
13
|
declare const VERSION: string;
|
|
11
14
|
|