nats.do 0.1.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 ADDED
@@ -0,0 +1,298 @@
1
+ # NATS.do
2
+
3
+ NATS/JetStream on Cloudflare Durable Objects
4
+
5
+ NATS.do implements [NATS](https://nats.io/) Core messaging and [JetStream](https://docs.nats.io/nats-concepts/jetstream) persistence using Cloudflare Workers and Durable Objects with SQLite storage. It provides a `nats.js`-compatible API accessible via JSON-RPC 2.0 over HTTP, WebSockets, or Cloudflare Workers Service Bindings.
6
+
7
+ ## Features
8
+
9
+ - **NATS Core**: Publish/subscribe messaging with subject wildcards (`*`, `>`)
10
+ - **JetStream Streams**: Persistent message storage with configurable retention policies
11
+ - **JetStream Consumers**: Pull and push consumers with acknowledgment tracking
12
+ - **MCP Integration**: Model Context Protocol tools for AI agent access
13
+ - **Edge-Native**: Runs entirely on Cloudflare's global network
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install nats.do
19
+ ```
20
+
21
+ ## Quick Start
22
+
23
+ ### Basic Pub/Sub
24
+
25
+ ```typescript
26
+ import { StringCodec, JSONCodec } from 'nats.do'
27
+
28
+ const sc = StringCodec()
29
+ const jc = JSONCodec()
30
+
31
+ // Publish a message
32
+ nc.publish('orders.new', sc.encode('Hello NATS!'))
33
+
34
+ // Subscribe with wildcards
35
+ const sub = nc.subscribe('orders.*')
36
+ for await (const msg of sub) {
37
+ console.log(`Received: ${sc.decode(msg.data)}`)
38
+ }
39
+
40
+ // Request/Reply pattern
41
+ const response = await nc.request('api.users.get', jc.encode({ id: 123 }))
42
+ console.log(jc.decode(response.data))
43
+ ```
44
+
45
+ ### JetStream Streams
46
+
47
+ ```typescript
48
+ // Create a stream
49
+ const jsm = nc.jetstreamManager()
50
+ await jsm.streams.add({
51
+ name: 'ORDERS',
52
+ subjects: ['orders.*'],
53
+ retention: 'workqueue',
54
+ max_msgs: 10000,
55
+ max_age: 24 * 60 * 60 * 1_000_000_000, // 24 hours in nanoseconds
56
+ })
57
+
58
+ // Publish with acknowledgment
59
+ const js = nc.jetstream()
60
+ const ack = await js.publish('orders.new', sc.encode('{"id": 456}'))
61
+ console.log(`Published to ${ack.stream} at seq ${ack.seq}`)
62
+ ```
63
+
64
+ ### JetStream Consumers
65
+
66
+ ```typescript
67
+ // Create a durable consumer
68
+ await jsm.consumers.add('ORDERS', {
69
+ durable_name: 'order-processor',
70
+ ack_policy: 'explicit',
71
+ deliver_policy: 'all',
72
+ })
73
+
74
+ // Fetch messages
75
+ const consumer = await js.consumers.get('ORDERS', 'order-processor')
76
+ const messages = await consumer.fetch({ max_messages: 10 })
77
+
78
+ for await (const msg of messages) {
79
+ console.log(`Processing: ${sc.decode(msg.data)}`)
80
+ msg.ack()
81
+ }
82
+ ```
83
+
84
+ ## API Reference
85
+
86
+ ### Core Types
87
+
88
+ ```typescript
89
+ // Connection options
90
+ interface ConnectionOptions {
91
+ servers: string | string[]
92
+ name?: string
93
+ token?: string
94
+ timeout?: number
95
+ }
96
+
97
+ // Message
98
+ interface Msg {
99
+ subject: string
100
+ data: Uint8Array
101
+ reply?: string
102
+ headers?: MsgHdrs
103
+ respond(data?: Uint8Array): boolean
104
+ }
105
+
106
+ // Subscription
107
+ interface Subscription extends AsyncIterable<Msg> {
108
+ getSubject(): string
109
+ unsubscribe(max?: number): void
110
+ drain(): Promise<void>
111
+ }
112
+ ```
113
+
114
+ ### JetStream Types
115
+
116
+ ```typescript
117
+ // Stream configuration
118
+ interface StreamConfig {
119
+ name: string
120
+ subjects: string[]
121
+ retention?: 'limits' | 'interest' | 'workqueue'
122
+ storage?: 'file' | 'memory'
123
+ max_msgs?: number
124
+ max_bytes?: number
125
+ max_age?: number // nanoseconds
126
+ discard?: 'old' | 'new'
127
+ }
128
+
129
+ // Consumer configuration
130
+ interface ConsumerConfig {
131
+ name?: string
132
+ durable_name?: string
133
+ ack_policy: 'none' | 'all' | 'explicit'
134
+ deliver_policy?: 'all' | 'last' | 'new' | 'by_start_sequence' | 'by_start_time'
135
+ filter_subject?: string
136
+ max_deliver?: number
137
+ ack_wait?: number // nanoseconds
138
+ }
139
+
140
+ // Publish acknowledgment
141
+ interface PubAck {
142
+ stream: string
143
+ seq: number
144
+ duplicate?: boolean
145
+ }
146
+ ```
147
+
148
+ ### Codecs
149
+
150
+ ```typescript
151
+ import { StringCodec, JSONCodec, Empty } from 'nats.do'
152
+
153
+ const sc = StringCodec()
154
+ sc.encode('hello') // Uint8Array
155
+ sc.decode(data) // string
156
+
157
+ const jc = JSONCodec<MyType>()
158
+ jc.encode({ key: 'value' }) // Uint8Array
159
+ jc.decode(data) // MyType
160
+
161
+ Empty // Empty Uint8Array for messages without payload
162
+ ```
163
+
164
+ ### Subject Wildcards
165
+
166
+ NATS.do supports NATS subject wildcards for subscriptions:
167
+
168
+ - `*` matches exactly one token: `orders.*` matches `orders.new` but not `orders.us.new`
169
+ - `>` matches one or more tokens (must be last): `orders.>` matches `orders.new` and `orders.us.new`
170
+
171
+ ```typescript
172
+ import { matchSubject, isValidSubject, isValidWildcard } from 'nats.do/utils'
173
+
174
+ matchSubject('orders.*', 'orders.new') // true
175
+ matchSubject('orders.*', 'orders.us.new') // false
176
+ matchSubject('orders.>', 'orders.us.new') // true
177
+ ```
178
+
179
+ ## Architecture
180
+
181
+ NATS.do uses three Durable Object classes:
182
+
183
+ | Durable Object | Scope | Responsibility |
184
+ |---------------|-------|----------------|
185
+ | `NatsCoordinator` | Global singleton | Stream registry, consumer discovery, cluster metadata |
186
+ | `NatsPubSub` | Per region | Core NATS pub/sub, WebSocket connections, request/reply |
187
+ | `StreamDO` | Per stream | Message storage, consumer state, ack tracking, retention |
188
+
189
+ ### RPC Protocol
190
+
191
+ NATS.do uses JSON-RPC 2.0 for communication:
192
+
193
+ ```typescript
194
+ // Request
195
+ {
196
+ "jsonrpc": "2.0",
197
+ "method": "nats.publish",
198
+ "params": { "subject": "orders.new", "data": "base64..." },
199
+ "id": 1
200
+ }
201
+
202
+ // Response
203
+ {
204
+ "jsonrpc": "2.0",
205
+ "result": { "success": true },
206
+ "id": 1
207
+ }
208
+ ```
209
+
210
+ ## MCP Tools
211
+
212
+ NATS.do exposes MCP (Model Context Protocol) tools for AI agent integration:
213
+
214
+ | Tool | Description |
215
+ |------|-------------|
216
+ | `nats_publish` | Publish a message to a subject |
217
+ | `nats_subscribe` | Subscribe to a subject |
218
+ | `nats_request` | Send a request and wait for response |
219
+ | `jetstream_publish` | Publish to JetStream with acknowledgment |
220
+ | `jetstream_stream_create` | Create a new stream |
221
+ | `jetstream_stream_info` | Get stream information |
222
+ | `jetstream_consumer_create` | Create a consumer |
223
+ | `jetstream_consumer_fetch` | Fetch messages from a consumer |
224
+
225
+ ## Cloudflare Workers Deployment
226
+
227
+ ### wrangler.jsonc
228
+
229
+ ```jsonc
230
+ {
231
+ "name": "nats.do",
232
+ "main": "src/index.ts",
233
+ "compatibility_date": "2024-01-01",
234
+ "compatibility_flags": ["nodejs_compat"],
235
+
236
+ "durable_objects": {
237
+ "bindings": [
238
+ { "name": "NATS_COORDINATOR", "class_name": "NatsCoordinator" },
239
+ { "name": "NATS_PUBSUB", "class_name": "NatsPubSub" },
240
+ { "name": "STREAM_DO", "class_name": "StreamDO" }
241
+ ]
242
+ },
243
+
244
+ "migrations": [
245
+ {
246
+ "tag": "v1",
247
+ "new_sqlite_classes": ["NatsCoordinator", "NatsPubSub", "StreamDO"]
248
+ }
249
+ ]
250
+ }
251
+ ```
252
+
253
+ ### Service Binding Usage
254
+
255
+ ```typescript
256
+ // In another Worker
257
+ export default {
258
+ async fetch(request: Request, env: Env) {
259
+ const id = env.NATS_COORDINATOR.idFromName('global')
260
+ const stub = env.NATS_COORDINATOR.get(id)
261
+
262
+ const response = await stub.fetch(new Request('http://internal/rpc', {
263
+ method: 'POST',
264
+ body: JSON.stringify({
265
+ jsonrpc: '2.0',
266
+ method: 'consumers.list',
267
+ params: { streamName: 'ORDERS' },
268
+ id: 1
269
+ })
270
+ }))
271
+
272
+ return response
273
+ }
274
+ }
275
+ ```
276
+
277
+ ## Development
278
+
279
+ ```bash
280
+ # Install dependencies
281
+ npm install
282
+
283
+ # Run tests
284
+ npm test
285
+
286
+ # Run tests in watch mode
287
+ npm run test:watch
288
+
289
+ # Type checking
290
+ npm run typecheck
291
+
292
+ # Local development
293
+ npm run dev
294
+ ```
295
+
296
+ ## License
297
+
298
+ MIT
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Cloudflare Workers Environment Type
3
+ *
4
+ * Defines the environment bindings for the NatDO worker,
5
+ * including Durable Object namespace bindings.
6
+ */
7
+ /**
8
+ * Environment interface for NatDO worker.
9
+ * Contains bindings for Durable Objects used in the application.
10
+ */
11
+ interface Env {
12
+ /**
13
+ * Durable Object namespace for NATS coordination.
14
+ * Manages consumer registry with SQLite storage.
15
+ */
16
+ NATS_COORDINATOR: DurableObjectNamespace;
17
+ /**
18
+ * Durable Object namespace for NATS publish/subscribe operations.
19
+ */
20
+ NATS_PUBSUB: DurableObjectNamespace;
21
+ /**
22
+ * Durable Object namespace for stream-specific operations.
23
+ * Each stream gets its own instance for fetching and acking messages.
24
+ */
25
+ STREAM_DO: DurableObjectNamespace;
26
+ }
27
+
28
+ export type { Env as E };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Cloudflare Workers Environment Type
3
+ *
4
+ * Defines the environment bindings for the NatDO worker,
5
+ * including Durable Object namespace bindings.
6
+ */
7
+ /**
8
+ * Environment interface for NatDO worker.
9
+ * Contains bindings for Durable Objects used in the application.
10
+ */
11
+ interface Env {
12
+ /**
13
+ * Durable Object namespace for NATS coordination.
14
+ * Manages consumer registry with SQLite storage.
15
+ */
16
+ NATS_COORDINATOR: DurableObjectNamespace;
17
+ /**
18
+ * Durable Object namespace for NATS publish/subscribe operations.
19
+ */
20
+ NATS_PUBSUB: DurableObjectNamespace;
21
+ /**
22
+ * Durable Object namespace for stream-specific operations.
23
+ * Each stream gets its own instance for fetching and acking messages.
24
+ */
25
+ STREAM_DO: DurableObjectNamespace;
26
+ }
27
+
28
+ export type { Env as E };
package/dist/index.cjs ADDED
@@ -0,0 +1,235 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, '__esModule', { value: true });
4
+
5
+ var cloudflare_workers = require('cloudflare:workers');
6
+
7
+ // src/durable-objects/nats-coordinator.ts
8
+
9
+ // src/types/rpc.ts
10
+ var RPC_ERROR_CODES = {
11
+ // Standard JSON-RPC errors
12
+ PARSE_ERROR: -32700,
13
+ METHOD_NOT_FOUND: -32601,
14
+ INVALID_PARAMS: -32602,
15
+ CONSUMER_NOT_FOUND: -32002,
16
+ CONSUMER_EXISTS: -32007};
17
+ function createRpcError(code, message, id, data) {
18
+ return {
19
+ jsonrpc: "2.0",
20
+ error: {
21
+ code,
22
+ message,
23
+ ...data !== void 0
24
+ },
25
+ id: id ?? null
26
+ };
27
+ }
28
+ function createRpcSuccess(result, id) {
29
+ return {
30
+ jsonrpc: "2.0",
31
+ result,
32
+ id
33
+ };
34
+ }
35
+
36
+ // src/durable-objects/nats-coordinator.ts
37
+ var NatsCoordinator = class extends cloudflare_workers.DurableObject {
38
+ sql;
39
+ constructor(ctx, env) {
40
+ super(ctx, env);
41
+ this.sql = ctx.storage.sql;
42
+ this.initSchema();
43
+ }
44
+ initSchema() {
45
+ this.sql.exec(`
46
+ CREATE TABLE IF NOT EXISTS consumers (
47
+ stream_name TEXT NOT NULL,
48
+ name TEXT NOT NULL,
49
+ config TEXT NOT NULL,
50
+ durable INTEGER NOT NULL DEFAULT 0,
51
+ created_at INTEGER NOT NULL,
52
+ last_active_at INTEGER,
53
+ PRIMARY KEY (stream_name, name)
54
+ )
55
+ `);
56
+ }
57
+ async fetch(request) {
58
+ if (request.method !== "POST") {
59
+ return new Response("Method not allowed", { status: 405 });
60
+ }
61
+ try {
62
+ const body = await request.json();
63
+ const { method, params, id } = body;
64
+ const result = await this.handleRpc(method, params || {}, id);
65
+ return new Response(JSON.stringify(result), {
66
+ headers: { "Content-Type": "application/json" }
67
+ });
68
+ } catch (error) {
69
+ const errorResponse = createRpcError(
70
+ RPC_ERROR_CODES.PARSE_ERROR,
71
+ "Invalid JSON",
72
+ null
73
+ );
74
+ return new Response(JSON.stringify(errorResponse), {
75
+ headers: { "Content-Type": "application/json" }
76
+ });
77
+ }
78
+ }
79
+ async handleRpc(method, params, id) {
80
+ switch (method) {
81
+ case "consumers.register":
82
+ return this.registerConsumer(params, id);
83
+ case "consumers.get":
84
+ return this.getConsumer(params, id);
85
+ case "consumers.delete":
86
+ return this.deleteConsumer(params, id);
87
+ case "consumers.list":
88
+ return this.listConsumers(params, id);
89
+ case "consumers.updateLastActive":
90
+ return this.updateConsumerLastActive(params, id);
91
+ default:
92
+ return createRpcError(
93
+ RPC_ERROR_CODES.METHOD_NOT_FOUND,
94
+ `Method not found: ${method}`,
95
+ id
96
+ );
97
+ }
98
+ }
99
+ registerConsumer(params, id) {
100
+ const streamName = params.streamName;
101
+ const config = params.config;
102
+ const consumerName = config.name || config.durable_name;
103
+ if (!consumerName) {
104
+ return createRpcError(
105
+ RPC_ERROR_CODES.INVALID_PARAMS,
106
+ "Consumer name is required (name or durable_name)",
107
+ id
108
+ );
109
+ }
110
+ const existing = this.sql.exec("SELECT 1 FROM consumers WHERE stream_name = ? AND name = ?", streamName, consumerName).toArray();
111
+ if (existing.length > 0) {
112
+ return createRpcError(
113
+ RPC_ERROR_CODES.CONSUMER_EXISTS,
114
+ `Consumer ${consumerName} already exists on stream ${streamName}`,
115
+ id
116
+ );
117
+ }
118
+ const now = Date.now();
119
+ const isDurable = !!config.durable_name;
120
+ this.sql.exec(
121
+ `INSERT INTO consumers (stream_name, name, config, durable, created_at, last_active_at)
122
+ VALUES (?, ?, ?, ?, ?, ?)`,
123
+ streamName,
124
+ consumerName,
125
+ JSON.stringify(config),
126
+ isDurable ? 1 : 0,
127
+ now,
128
+ null
129
+ );
130
+ const entry = {
131
+ stream_name: streamName,
132
+ name: consumerName,
133
+ config,
134
+ durable: isDurable,
135
+ created_at: now,
136
+ last_active_at: null
137
+ };
138
+ return createRpcSuccess(entry, id);
139
+ }
140
+ getConsumer(params, id) {
141
+ const streamName = params.streamName;
142
+ const consumerName = params.consumerName;
143
+ const rows = this.sql.exec(
144
+ "SELECT stream_name, name, config, durable, created_at, last_active_at FROM consumers WHERE stream_name = ? AND name = ?",
145
+ streamName,
146
+ consumerName
147
+ ).toArray();
148
+ if (rows.length === 0) {
149
+ return createRpcError(
150
+ RPC_ERROR_CODES.CONSUMER_NOT_FOUND,
151
+ `Consumer ${consumerName} not found on stream ${streamName}`,
152
+ id
153
+ );
154
+ }
155
+ const row = rows[0];
156
+ const entry = {
157
+ stream_name: row.stream_name,
158
+ name: row.name,
159
+ config: JSON.parse(row.config),
160
+ durable: row.durable === 1,
161
+ created_at: row.created_at,
162
+ last_active_at: row.last_active_at
163
+ };
164
+ return createRpcSuccess(entry, id);
165
+ }
166
+ deleteConsumer(params, id) {
167
+ const streamName = params.streamName;
168
+ const consumerName = params.consumerName;
169
+ const existing = this.sql.exec("SELECT 1 FROM consumers WHERE stream_name = ? AND name = ?", streamName, consumerName).toArray();
170
+ if (existing.length === 0) {
171
+ return createRpcError(
172
+ RPC_ERROR_CODES.CONSUMER_NOT_FOUND,
173
+ `Consumer ${consumerName} not found on stream ${streamName}`,
174
+ id
175
+ );
176
+ }
177
+ this.sql.exec(
178
+ "DELETE FROM consumers WHERE stream_name = ? AND name = ?",
179
+ streamName,
180
+ consumerName
181
+ );
182
+ return createRpcSuccess({ success: true }, id);
183
+ }
184
+ listConsumers(params, id) {
185
+ const streamName = params.streamName;
186
+ const rows = this.sql.exec(
187
+ "SELECT stream_name, name, config, durable, created_at, last_active_at FROM consumers WHERE stream_name = ? ORDER BY name",
188
+ streamName
189
+ ).toArray();
190
+ const consumers = rows.map((row) => {
191
+ const r = row;
192
+ return {
193
+ stream_name: r.stream_name,
194
+ name: r.name,
195
+ config: JSON.parse(r.config),
196
+ durable: r.durable === 1,
197
+ created_at: r.created_at,
198
+ last_active_at: r.last_active_at
199
+ };
200
+ });
201
+ return createRpcSuccess(consumers, id);
202
+ }
203
+ updateConsumerLastActive(params, id) {
204
+ const streamName = params.streamName;
205
+ const consumerName = params.consumerName;
206
+ const existing = this.sql.exec("SELECT 1 FROM consumers WHERE stream_name = ? AND name = ?", streamName, consumerName).toArray();
207
+ if (existing.length === 0) {
208
+ return createRpcError(
209
+ RPC_ERROR_CODES.CONSUMER_NOT_FOUND,
210
+ `Consumer ${consumerName} not found on stream ${streamName}`,
211
+ id
212
+ );
213
+ }
214
+ const now = Date.now();
215
+ this.sql.exec(
216
+ "UPDATE consumers SET last_active_at = ? WHERE stream_name = ? AND name = ?",
217
+ now,
218
+ streamName,
219
+ consumerName
220
+ );
221
+ return createRpcSuccess({ last_active_at: now }, id);
222
+ }
223
+ };
224
+
225
+ // src/index.ts
226
+ var src_default = {
227
+ async fetch(_request, _env) {
228
+ return new Response("NatDO - Not yet implemented", { status: 501 });
229
+ }
230
+ };
231
+
232
+ exports.NatsCoordinator = NatsCoordinator;
233
+ exports.default = src_default;
234
+ //# sourceMappingURL=index.cjs.map
235
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types/rpc.ts","../src/durable-objects/nats-coordinator.ts","../src/index.ts"],"names":["DurableObject"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * RPC Types\n *\n * JSON-RPC 2.0 types for NatDO communication.\n */\n\n// Request ID type\nexport type RpcId = string | number\n\n// RPC Request\nexport interface RpcRequest {\n jsonrpc: '2.0'\n method: string\n params?: Record<string, unknown> | unknown[]\n id: RpcId\n}\n\n// RPC Notification (no id)\nexport interface RpcNotification {\n jsonrpc: '2.0'\n method: string\n params?: Record<string, unknown> | unknown[]\n}\n\n// RPC Error\nexport interface RpcError {\n code: number\n message: string\n data?: unknown\n}\n\n// RPC Response\nexport interface RpcResponse {\n jsonrpc: '2.0'\n result?: unknown\n error?: RpcError\n id: RpcId | null\n}\n\n// Batch types\nexport type RpcBatchRequest = Array<RpcRequest | RpcNotification>\nexport type RpcBatchResponse = RpcResponse[]\n\n// Standard JSON-RPC 2.0 error codes\nexport const RPC_ERROR_CODES = {\n // Standard JSON-RPC errors\n PARSE_ERROR: -32700,\n INVALID_REQUEST: -32600,\n METHOD_NOT_FOUND: -32601,\n INVALID_PARAMS: -32602,\n INTERNAL_ERROR: -32603,\n\n // NATS-specific error codes (-32001 to -32099)\n STREAM_NOT_FOUND: -32001,\n CONSUMER_NOT_FOUND: -32002,\n NO_RESPONDERS: -32003,\n TIMEOUT: -32004,\n MESSAGE_NOT_FOUND: -32005,\n STREAM_EXISTS: -32006,\n CONSUMER_EXISTS: -32007,\n INVALID_SUBJECT: -32008,\n PERMISSION_DENIED: -32009,\n MAX_PAYLOAD_EXCEEDED: -32010,\n DUPLICATE_MESSAGE: -32011,\n STREAM_SEALED: -32012,\n CONSUMER_DELETED: -32013,\n} as const\n\n// Type guard for error responses\nexport function isRpcError(response: RpcResponse): response is RpcResponse & { error: RpcError } {\n return 'error' in response && response.error !== undefined\n}\n\n// Type guard for success responses\nexport function isRpcSuccess(response: RpcResponse): response is RpcResponse & { result: unknown } {\n return !isRpcError(response)\n}\n\n// Request ID counter for auto-incrementing IDs\nlet requestIdCounter = 0\n\n// Create RPC request helper\nexport function createRpcRequest(\n method: string,\n opts?: { params?: Record<string, unknown> | unknown[]; id?: RpcId }\n): RpcRequest {\n return {\n jsonrpc: '2.0',\n method,\n ...(opts?.params && { params: opts.params }),\n id: opts?.id ?? ++requestIdCounter,\n }\n}\n\n// Create RPC error response helper\nexport function createRpcError(\n code: number,\n message: string,\n id?: RpcId | null,\n data?: unknown\n): RpcResponse {\n return {\n jsonrpc: '2.0',\n error: {\n code,\n message,\n ...(data !== undefined && { data }),\n },\n id: id ?? null,\n }\n}\n\n// Create RPC success response helper\nexport function createRpcSuccess(result: unknown, id: RpcId): RpcResponse {\n return {\n jsonrpc: '2.0',\n result,\n id,\n }\n}\n\n// Reset request ID counter (for testing)\nexport function resetRequestIdCounter(): void {\n requestIdCounter = 0\n}\n","/**\n * NatsCoordinator Durable Object\n *\n * Central coordinator for NATS/JetStream operations.\n * Manages consumer registry with SQLite storage.\n */\n\nimport { DurableObject } from 'cloudflare:workers'\nimport type { ConsumerConfig } from '../types/jetstream'\nimport { RPC_ERROR_CODES, createRpcError, createRpcSuccess } from '../types/rpc'\nimport type { Env } from '../types/env'\n\n// Consumer registry entry stored in SQLite\ninterface ConsumerEntry {\n stream_name: string\n name: string\n config: ConsumerConfig\n durable: boolean\n created_at: number\n last_active_at: number | null\n}\n\n// RPC request structure\ninterface RpcRequest {\n jsonrpc: '2.0'\n method: string\n params?: Record<string, unknown>\n id: number | string\n}\n\nexport class NatsCoordinator extends DurableObject<Env> {\n private sql: SqlStorage\n\n constructor(ctx: DurableObjectState, env: Env) {\n super(ctx, env)\n this.sql = ctx.storage.sql\n\n // Initialize schema\n this.initSchema()\n }\n\n private initSchema(): void {\n this.sql.exec(`\n CREATE TABLE IF NOT EXISTS consumers (\n stream_name TEXT NOT NULL,\n name TEXT NOT NULL,\n config TEXT NOT NULL,\n durable INTEGER NOT NULL DEFAULT 0,\n created_at INTEGER NOT NULL,\n last_active_at INTEGER,\n PRIMARY KEY (stream_name, name)\n )\n `)\n }\n\n async fetch(request: Request): Promise<Response> {\n if (request.method !== 'POST') {\n return new Response('Method not allowed', { status: 405 })\n }\n\n try {\n const body = await request.json() as RpcRequest\n const { method, params, id } = body\n\n const result = await this.handleRpc(method, params || {}, id)\n return new Response(JSON.stringify(result), {\n headers: { 'Content-Type': 'application/json' },\n })\n } catch (error) {\n const errorResponse = createRpcError(\n RPC_ERROR_CODES.PARSE_ERROR,\n 'Invalid JSON',\n null\n )\n return new Response(JSON.stringify(errorResponse), {\n headers: { 'Content-Type': 'application/json' },\n })\n }\n }\n\n private async handleRpc(\n method: string,\n params: Record<string, unknown>,\n id: number | string\n ) {\n switch (method) {\n case 'consumers.register':\n return this.registerConsumer(params, id)\n case 'consumers.get':\n return this.getConsumer(params, id)\n case 'consumers.delete':\n return this.deleteConsumer(params, id)\n case 'consumers.list':\n return this.listConsumers(params, id)\n case 'consumers.updateLastActive':\n return this.updateConsumerLastActive(params, id)\n default:\n return createRpcError(\n RPC_ERROR_CODES.METHOD_NOT_FOUND,\n `Method not found: ${method}`,\n id\n )\n }\n }\n\n private registerConsumer(params: Record<string, unknown>, id: number | string) {\n const streamName = params.streamName as string\n const config = params.config as ConsumerConfig\n\n // Validate consumer name\n const consumerName = config.name || config.durable_name\n if (!consumerName) {\n return createRpcError(\n RPC_ERROR_CODES.INVALID_PARAMS,\n 'Consumer name is required (name or durable_name)',\n id\n )\n }\n\n // Check if consumer already exists\n const existing = this.sql\n .exec('SELECT 1 FROM consumers WHERE stream_name = ? AND name = ?', streamName, consumerName)\n .toArray()\n\n if (existing.length > 0) {\n return createRpcError(\n RPC_ERROR_CODES.CONSUMER_EXISTS,\n `Consumer ${consumerName} already exists on stream ${streamName}`,\n id\n )\n }\n\n const now = Date.now()\n const isDurable = !!config.durable_name\n\n // Store the consumer\n this.sql.exec(\n `INSERT INTO consumers (stream_name, name, config, durable, created_at, last_active_at)\n VALUES (?, ?, ?, ?, ?, ?)`,\n streamName,\n consumerName,\n JSON.stringify(config),\n isDurable ? 1 : 0,\n now,\n null\n )\n\n const entry: ConsumerEntry = {\n stream_name: streamName,\n name: consumerName,\n config,\n durable: isDurable,\n created_at: now,\n last_active_at: null,\n }\n\n return createRpcSuccess(entry, id)\n }\n\n private getConsumer(params: Record<string, unknown>, id: number | string) {\n const streamName = params.streamName as string\n const consumerName = params.consumerName as string\n\n const rows = this.sql\n .exec(\n 'SELECT stream_name, name, config, durable, created_at, last_active_at FROM consumers WHERE stream_name = ? AND name = ?',\n streamName,\n consumerName\n )\n .toArray()\n\n if (rows.length === 0) {\n return createRpcError(\n RPC_ERROR_CODES.CONSUMER_NOT_FOUND,\n `Consumer ${consumerName} not found on stream ${streamName}`,\n id\n )\n }\n\n const row = rows[0] as {\n stream_name: string\n name: string\n config: string\n durable: number\n created_at: number\n last_active_at: number | null\n }\n\n const entry: ConsumerEntry = {\n stream_name: row.stream_name,\n name: row.name,\n config: JSON.parse(row.config) as ConsumerConfig,\n durable: row.durable === 1,\n created_at: row.created_at,\n last_active_at: row.last_active_at,\n }\n\n return createRpcSuccess(entry, id)\n }\n\n private deleteConsumer(params: Record<string, unknown>, id: number | string) {\n const streamName = params.streamName as string\n const consumerName = params.consumerName as string\n\n // Check if consumer exists\n const existing = this.sql\n .exec('SELECT 1 FROM consumers WHERE stream_name = ? AND name = ?', streamName, consumerName)\n .toArray()\n\n if (existing.length === 0) {\n return createRpcError(\n RPC_ERROR_CODES.CONSUMER_NOT_FOUND,\n `Consumer ${consumerName} not found on stream ${streamName}`,\n id\n )\n }\n\n this.sql.exec(\n 'DELETE FROM consumers WHERE stream_name = ? AND name = ?',\n streamName,\n consumerName\n )\n\n return createRpcSuccess({ success: true }, id)\n }\n\n private listConsumers(params: Record<string, unknown>, id: number | string) {\n const streamName = params.streamName as string\n\n const rows = this.sql\n .exec(\n 'SELECT stream_name, name, config, durable, created_at, last_active_at FROM consumers WHERE stream_name = ? ORDER BY name',\n streamName\n )\n .toArray()\n\n const consumers: ConsumerEntry[] = rows.map((row) => {\n const r = row as {\n stream_name: string\n name: string\n config: string\n durable: number\n created_at: number\n last_active_at: number | null\n }\n return {\n stream_name: r.stream_name,\n name: r.name,\n config: JSON.parse(r.config) as ConsumerConfig,\n durable: r.durable === 1,\n created_at: r.created_at,\n last_active_at: r.last_active_at,\n }\n })\n\n return createRpcSuccess(consumers, id)\n }\n\n private updateConsumerLastActive(params: Record<string, unknown>, id: number | string) {\n const streamName = params.streamName as string\n const consumerName = params.consumerName as string\n\n // Check if consumer exists\n const existing = this.sql\n .exec('SELECT 1 FROM consumers WHERE stream_name = ? AND name = ?', streamName, consumerName)\n .toArray()\n\n if (existing.length === 0) {\n return createRpcError(\n RPC_ERROR_CODES.CONSUMER_NOT_FOUND,\n `Consumer ${consumerName} not found on stream ${streamName}`,\n id\n )\n }\n\n const now = Date.now()\n this.sql.exec(\n 'UPDATE consumers SET last_active_at = ? WHERE stream_name = ? AND name = ?',\n now,\n streamName,\n consumerName\n )\n\n return createRpcSuccess({ last_active_at: now }, id)\n }\n}\n","/**\n * NatDO - NATS/JetStream on Cloudflare Durable Objects\n *\n * This is the main entry point for the worker.\n */\n\nexport { NatsCoordinator } from './durable-objects/nats-coordinator'\n\nexport default {\n async fetch(_request: Request, _env: unknown): Promise<Response> {\n return new Response('NatDO - Not yet implemented', { status: 501 })\n },\n}\n"]}
@@ -0,0 +1,34 @@
1
+ import { DurableObject } from 'cloudflare:workers';
2
+ import { E as Env } from './env-Ds3wIkQg.cjs';
3
+
4
+ /**
5
+ * NatsCoordinator Durable Object
6
+ *
7
+ * Central coordinator for NATS/JetStream operations.
8
+ * Manages consumer registry with SQLite storage.
9
+ */
10
+
11
+ declare class NatsCoordinator extends DurableObject<Env> {
12
+ private sql;
13
+ constructor(ctx: DurableObjectState, env: Env);
14
+ private initSchema;
15
+ fetch(request: Request): Promise<Response>;
16
+ private handleRpc;
17
+ private registerConsumer;
18
+ private getConsumer;
19
+ private deleteConsumer;
20
+ private listConsumers;
21
+ private updateConsumerLastActive;
22
+ }
23
+
24
+ /**
25
+ * NatDO - NATS/JetStream on Cloudflare Durable Objects
26
+ *
27
+ * This is the main entry point for the worker.
28
+ */
29
+
30
+ declare const _default: {
31
+ fetch(_request: Request, _env: unknown): Promise<Response>;
32
+ };
33
+
34
+ export { NatsCoordinator, _default as default };
@@ -0,0 +1,34 @@
1
+ import { DurableObject } from 'cloudflare:workers';
2
+ import { E as Env } from './env-Ds3wIkQg.js';
3
+
4
+ /**
5
+ * NatsCoordinator Durable Object
6
+ *
7
+ * Central coordinator for NATS/JetStream operations.
8
+ * Manages consumer registry with SQLite storage.
9
+ */
10
+
11
+ declare class NatsCoordinator extends DurableObject<Env> {
12
+ private sql;
13
+ constructor(ctx: DurableObjectState, env: Env);
14
+ private initSchema;
15
+ fetch(request: Request): Promise<Response>;
16
+ private handleRpc;
17
+ private registerConsumer;
18
+ private getConsumer;
19
+ private deleteConsumer;
20
+ private listConsumers;
21
+ private updateConsumerLastActive;
22
+ }
23
+
24
+ /**
25
+ * NatDO - NATS/JetStream on Cloudflare Durable Objects
26
+ *
27
+ * This is the main entry point for the worker.
28
+ */
29
+
30
+ declare const _default: {
31
+ fetch(_request: Request, _env: unknown): Promise<Response>;
32
+ };
33
+
34
+ export { NatsCoordinator, _default as default };