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/dist/worker.d.ts CHANGED
@@ -1,8 +1,10 @@
1
1
  import { z } from 'zod';
2
- import { e as Notifier, g as createDispatcher, D as D1Like } from './d1-CUcNko0m.js';
3
- import { R as Recipient } from './types-GuAgyMOc.js';
4
- import './gdpr-9I4eV86m.js';
5
- import './types-Dkst26cb.js';
2
+ import { c as Notifier, e as createDispatcher } from './dispatch-xe2IAqWe.js';
3
+ import { R as Recipient } from './types-anA8DMQH.js';
4
+ import { D1Like } from 'agentic-identity/cloudflare';
5
+ import './gdpr-BAg1U1w6.js';
6
+ import 'agentic-identity';
7
+ import './types-BdX7UTFu.js';
6
8
 
7
9
  /** Who is calling. `kind` has no default: a missing kind is an error. */
8
10
  declare const principalSchema: z.ZodObject<{
@@ -11,12 +13,12 @@ declare const principalSchema: z.ZodObject<{
11
13
  /** Allowed to send notifications to others. */
12
14
  canSend: z.ZodOptional<z.ZodBoolean>;
13
15
  }, "strict", z.ZodTypeAny, {
14
- id: string;
15
16
  kind: "human" | "agent";
17
+ id: string;
16
18
  canSend?: boolean | undefined;
17
19
  }, {
18
- id: string;
19
20
  kind: "human" | "agent";
21
+ id: string;
20
22
  canSend?: boolean | undefined;
21
23
  }>;
22
24
  type Principal = z.infer<typeof principalSchema>;
@@ -38,6 +40,8 @@ interface WorkerEnv {
38
40
  DB?: D1Like;
39
41
  /** Secret. JSON map of bearer token (32 bytes or more) to principal. */
40
42
  NOTIFY_TOKENS?: string;
43
+ /** Data region of the store: "eu", "us" or "unknown". Default "eu". */
44
+ NOTIFY_REGION?: string;
41
45
  /** Retention window in days. Unset keeps notifications. */
42
46
  NOTIFY_RETENTION_DAYS?: string;
43
47
  }
package/dist/worker.js CHANGED
@@ -1,15 +1,15 @@
1
1
  import {
2
- D1Store,
2
+ NOTES,
3
3
  NotFoundError,
4
- VERSION,
5
4
  createNotifier,
6
- migrateD1,
7
5
  preferencesSchema,
8
6
  recipientSchema,
9
7
  resourceRefSchema,
10
8
  typeNameSchema
11
- } from "./chunk-FMU2ED2H.js";
12
- import "./chunk-GJFAXI2K.js";
9
+ } from "./chunk-ARL7FN6Y.js";
10
+ import {
11
+ VERSION
12
+ } from "./chunk-PHY45IVQ.js";
13
13
 
14
14
  // src/api.ts
15
15
  import { Hono } from "hono";
@@ -113,17 +113,27 @@ function createApp(options) {
113
113
  }
114
114
 
115
115
  // src/worker.ts
116
+ import { parseRegion } from "agentic-identity";
117
+ import { createD1Store, D1_SCHEMA } from "agentic-identity/cloudflare";
116
118
  function json(value, status = 200) {
117
119
  return new Response(JSON.stringify(value), { status, headers: { "content-type": "application/json" } });
118
120
  }
121
+ async function hasLegacyRows(db) {
122
+ const table = await db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'notify_docs'").bind().first();
123
+ if (!table) return false;
124
+ return await db.prepare("SELECT 1 AS found FROM notify_docs LIMIT 1").bind().first() !== null;
125
+ }
119
126
  function createWorker(config) {
120
127
  const apps = /* @__PURE__ */ new WeakMap();
121
128
  async function buildNotifier(env) {
122
129
  if (!env.DB) return null;
123
- await migrateD1(env.DB);
130
+ if (await hasLegacyRows(env.DB)) throw new Error("notify_docs holds rows that were not copied to store_docs");
131
+ await env.DB.prepare(D1_SCHEMA).bind().run();
124
132
  const days = env.NOTIFY_RETENTION_DAYS === void 0 ? void 0 : Number(env.NOTIFY_RETENTION_DAYS);
133
+ const store = createD1Store(env.DB, { region: parseRegion(env.NOTIFY_REGION ?? "eu") });
134
+ await store.query(NOTES, { limit: 1 });
125
135
  return createNotifier({
126
- store: new D1Store(env.DB),
136
+ store,
127
137
  retentionMs: days === void 0 ? void 0 : days * 864e5
128
138
  });
129
139
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-notify",
3
- "version": "0.1.0",
3
+ "version": "0.2.1",
4
4
  "description": "Notifications for humans and agents: in-app inbox, webhook and email delivery, with SDK, CLI, MCP server and Worker API.",
5
5
  "keywords": [
6
6
  "notifications",
@@ -61,10 +61,12 @@
61
61
  "clean": "rm -rf dist",
62
62
  "prepack": "npm run build",
63
63
  "ci:local": "npm run build && npm run typecheck && npm run check:worker && npm test",
64
+ "deploy:demo": "wrangler deploy --config wrangler.demo.jsonc",
64
65
  "check:worker": "esbuild src/worker.ts --bundle --platform=neutral --format=esm --log-level=warning --outfile=node_modules/.cache/worker-check.js"
65
66
  },
66
67
  "dependencies": {
67
68
  "@modelcontextprotocol/sdk": "^1.12.0",
69
+ "agentic-identity": "^0.4.0",
68
70
  "hono": "^4.13.14",
69
71
  "zod": "^3.25.0"
70
72
  },
@@ -74,7 +76,8 @@
74
76
  "miniflare": "^4.20260730.0",
75
77
  "tsup": "^8.0.0",
76
78
  "typescript": "^5.6.0",
77
- "vitest": "^3.0.0"
79
+ "vitest": "^3.0.0",
80
+ "wrangler": "^4.155.0"
78
81
  },
79
82
  "peerDependencies": {
80
83
  "vitest": ">=3"
@@ -1,86 +0,0 @@
1
- import {
2
- PACKAGE_NAME
3
- } from "./chunk-6CKFBZOF.js";
4
- import {
5
- ApiError
6
- } from "./chunk-FJPEUDTG.js";
7
- import {
8
- VERSION,
9
- preferencesSchema,
10
- recipientSchema,
11
- resourceRefSchema,
12
- typeNameSchema
13
- } from "./chunk-FMU2ED2H.js";
14
-
15
- // src/mcp.ts
16
- import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
17
- import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
18
- import { z } from "zod";
19
- async function wrap(fn) {
20
- try {
21
- return { content: [{ type: "text", text: JSON.stringify(await fn()) }] };
22
- } catch (error) {
23
- if (error instanceof ApiError) {
24
- return { content: [{ type: "text", text: JSON.stringify({ error: error.code, status: error.status }) }], isError: true };
25
- }
26
- throw error;
27
- }
28
- }
29
- function createMcpServer(client) {
30
- const server = new McpServer({ name: PACKAGE_NAME, version: VERSION });
31
- const need = (fn) => () => wrap(async () => {
32
- if (!client) throw new ApiError(0, "not_configured");
33
- return fn(client);
34
- });
35
- const read = { readOnlyHint: true };
36
- const write = { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false };
37
- server.registerTool(
38
- "ping",
39
- { description: "Check that the server runs. Returns the package version.", inputSchema: {}, annotations: read },
40
- async () => ({ content: [{ type: "text", text: JSON.stringify({ ok: true, version: VERSION }) }] })
41
- );
42
- server.registerTool("list_notifications", {
43
- description: "List your notifications, newest first.",
44
- inputSchema: { unreadOnly: z.boolean().optional(), limit: z.number().int().min(1).max(200).optional(), cursor: z.string().optional() },
45
- annotations: read
46
- }, (a) => need((c) => c.list(a))());
47
- server.registerTool(
48
- "read_notification",
49
- { description: "Get one notification by id.", inputSchema: { id: z.string() }, annotations: read },
50
- ({ id }) => need((c) => c.get(id))()
51
- );
52
- server.registerTool(
53
- "mark_read",
54
- { description: "Mark one notification as read.", inputSchema: { id: z.string() }, annotations: write },
55
- ({ id }) => need((c) => c.markRead(id))()
56
- );
57
- server.registerTool(
58
- "mark_all_read",
59
- { description: "Mark all your notifications as read.", inputSchema: {}, annotations: write },
60
- need((c) => c.markAllRead())
61
- );
62
- server.registerTool(
63
- "get_preferences",
64
- { description: "Read your notification preferences.", inputSchema: {}, annotations: read },
65
- need((c) => c.getPreferences())
66
- );
67
- server.registerTool("set_preferences", {
68
- description: "Replace your notification preferences: channels per type, mute, muted types.",
69
- inputSchema: preferencesSchema.shape,
70
- annotations: write
71
- }, (a) => need((c) => c.setPreferences(a))());
72
- server.registerTool("send_notification", {
73
- description: "Send a notification. You must hold the send right. You are the actor.",
74
- inputSchema: { recipient: recipientSchema, type: typeNameSchema, resource: resourceRefSchema, body: z.string().max(2e3) },
75
- annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false }
76
- }, (a) => need((c) => c.send(a))());
77
- return server;
78
- }
79
- async function startMcp(client) {
80
- await createMcpServer(client).connect(new StdioServerTransport());
81
- }
82
-
83
- export {
84
- createMcpServer,
85
- startMcp
86
- };
@@ -1,104 +0,0 @@
1
- // src/core/store.ts
2
- var MAX_LIMIT = 200;
3
- var DEFAULT_LIMIT = 50;
4
- function clampLimit(limit) {
5
- if (limit === void 0) return DEFAULT_LIMIT;
6
- if (!Number.isInteger(limit) || limit < 1) {
7
- throw new RangeError("limit must be an integer of 1 or more");
8
- }
9
- return Math.min(limit, MAX_LIMIT);
10
- }
11
- function encodeCursor(value, id) {
12
- return btoa(unescape(encodeURIComponent(JSON.stringify([value, id]))));
13
- }
14
- function decodeCursor(cursor) {
15
- try {
16
- const parsed = JSON.parse(decodeURIComponent(escape(atob(cursor))));
17
- if (Array.isArray(parsed) && parsed.length === 2 && typeof parsed[1] === "string" && (parsed[0] === null || ["string", "number", "boolean"].includes(typeof parsed[0]))) {
18
- return [parsed[0], parsed[1]];
19
- }
20
- } catch {
21
- }
22
- throw new RangeError("bad cursor");
23
- }
24
- function compare(a, b) {
25
- if (a === b) return 0;
26
- if (a === null) return -1;
27
- if (b === null) return 1;
28
- return a < b ? -1 : 1;
29
- }
30
- function canonicalJson(value) {
31
- return JSON.stringify(
32
- value,
33
- (_k, v) => v && typeof v === "object" && !Array.isArray(v) ? Object.fromEntries(Object.entries(v).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) : v
34
- );
35
- }
36
- function sameDoc(a, b) {
37
- return canonicalJson(a) === canonicalJson(b);
38
- }
39
- var MemoryStore = class {
40
- data = /* @__PURE__ */ new Map();
41
- coll(name) {
42
- let c = this.data.get(name);
43
- if (!c) {
44
- c = /* @__PURE__ */ new Map();
45
- this.data.set(name, c);
46
- }
47
- return c;
48
- }
49
- async get(collection, id) {
50
- const doc = this.coll(collection).get(id);
51
- return doc ? structuredClone(doc) : null;
52
- }
53
- async put(collection, id, doc) {
54
- this.coll(collection).set(id, structuredClone(doc));
55
- }
56
- async create(collection, id, doc) {
57
- const c = this.coll(collection);
58
- if (c.has(id)) return false;
59
- c.set(id, structuredClone(doc));
60
- return true;
61
- }
62
- async replaceIf(collection, id, expected, next) {
63
- const c = this.coll(collection);
64
- const current = c.get(id);
65
- if (!current || !sameDoc(current, expected)) return false;
66
- c.set(id, structuredClone(next));
67
- return true;
68
- }
69
- async delete(collection, id) {
70
- this.coll(collection).delete(id);
71
- }
72
- async query(collection, query = {}) {
73
- const limit = clampLimit(query.limit);
74
- const field = query.orderBy?.field;
75
- const dir = query.orderBy?.direction === "desc" ? -1 : 1;
76
- const where = Object.entries(query.where ?? {});
77
- const key = (id, doc) => field ? doc[field] ?? null : id;
78
- let rows = [...this.coll(collection)].filter(([, doc]) => where.every(([k, v]) => (doc[k] ?? null) === v)).sort(([ia, a], [ib, b]) => dir * (compare(key(ia, a), key(ib, b)) || compare(ia, ib)));
79
- if (query.cursor !== void 0) {
80
- const [cv, cid] = decodeCursor(query.cursor);
81
- const kind = (v) => v === null ? "null" : typeof v;
82
- if (rows.some(([id, doc]) => key(id, doc) !== null && cv !== null && kind(key(id, doc)) !== kind(cv))) {
83
- throw new RangeError("bad cursor");
84
- }
85
- rows = rows.filter(([id, doc]) => dir * (compare(key(id, doc), cv) || compare(id, cid)) > 0);
86
- }
87
- const slice = rows.slice(0, limit + 1);
88
- const hasMore = slice.length > limit;
89
- const items = slice.slice(0, limit).map(([id, doc]) => ({ id, doc: structuredClone(doc) }));
90
- const last = items[items.length - 1];
91
- return {
92
- items,
93
- nextCursor: hasMore && last ? encodeCursor(key(last.id, last.doc), last.id) : null
94
- };
95
- }
96
- };
97
-
98
- export {
99
- clampLimit,
100
- encodeCursor,
101
- decodeCursor,
102
- canonicalJson,
103
- MemoryStore
104
- };
@@ -1,69 +0,0 @@
1
- import { N as Notification } from './types-GuAgyMOc.js';
2
-
3
- /**
4
- * Store port. Documents are flat JSON objects in named collections.
5
- * The shape fits document stores and SQL alike: equality filters, one
6
- * orderBy, a limit, cursor paging. No joins.
7
- */
8
- type Scalar = string | number | boolean | null;
9
- type Doc = Record<string, unknown>;
10
- interface Query {
11
- /** Equality filters on top-level scalar fields. */
12
- where?: Record<string, Scalar>;
13
- /** One sort field. Ties break by id, in the same direction. */
14
- orderBy?: {
15
- field: string;
16
- direction: "asc" | "desc";
17
- };
18
- limit?: number;
19
- /** Opaque value from a previous page. */
20
- cursor?: string;
21
- }
22
- interface Page {
23
- items: {
24
- id: string;
25
- doc: Doc;
26
- }[];
27
- nextCursor: string | null;
28
- }
29
- interface Store {
30
- get(collection: string, id: string): Promise<Doc | null>;
31
- /** Insert or replace. */
32
- put(collection: string, id: string, doc: Doc): Promise<void>;
33
- /** Insert only. Returns false when the id exists. Atomic. */
34
- create(collection: string, id: string, doc: Doc): Promise<boolean>;
35
- /** Replace only when the stored doc still equals `expected`. Equality is structural: key order does not matter. Atomic. */
36
- replaceIf(collection: string, id: string, expected: Doc, next: Doc): Promise<boolean>;
37
- delete(collection: string, id: string): Promise<void>;
38
- query(collection: string, query?: Query): Promise<Page>;
39
- }
40
- /** In-memory adapter. Also the reference for the contract suite. */
41
- declare class MemoryStore implements Store {
42
- private readonly data;
43
- private coll;
44
- get(collection: string, id: string): Promise<Doc | null>;
45
- put(collection: string, id: string, doc: Doc): Promise<void>;
46
- create(collection: string, id: string, doc: Doc): Promise<boolean>;
47
- replaceIf(collection: string, id: string, expected: Doc, next: Doc): Promise<boolean>;
48
- delete(collection: string, id: string): Promise<void>;
49
- query(collection: string, query?: Query): Promise<Page>;
50
- }
51
-
52
- /** A delivery channel other than the inbox. */
53
- interface DeliveryChannel {
54
- /** Resolves "skipped" when the recipient has no target (no endpoint, no address). Throws on failure. */
55
- deliver(notification: Notification): Promise<"sent" | "skipped">;
56
- }
57
- declare class DeliveryError extends Error {
58
- readonly attempts: number;
59
- readonly code = "DELIVERY_FAILED";
60
- constructor(message: string, attempts: number, options?: {
61
- cause?: unknown;
62
- });
63
- }
64
- /** Logger that takes ids and counts only. Never pass content or addresses. */
65
- interface Logger {
66
- warn(message: string, fields?: Record<string, string | number>): void;
67
- }
68
-
69
- export { type DeliveryChannel as D, type Logger as L, MemoryStore as M, type Page as P, type Query as Q, type Store as S, DeliveryError as a, type Doc as b, type Scalar as c };