cooper-stack 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.
Files changed (97) hide show
  1. package/dist/ai.d.ts +60 -0
  2. package/dist/ai.d.ts.map +1 -0
  3. package/dist/ai.js +66 -0
  4. package/dist/ai.js.map +1 -0
  5. package/dist/api.d.ts +31 -0
  6. package/dist/api.d.ts.map +1 -0
  7. package/dist/api.js +40 -0
  8. package/dist/api.js.map +1 -0
  9. package/dist/auth.d.ts +13 -0
  10. package/dist/auth.d.ts.map +1 -0
  11. package/dist/auth.js +16 -0
  12. package/dist/auth.js.map +1 -0
  13. package/dist/bridge.d.ts +15 -0
  14. package/dist/bridge.d.ts.map +1 -0
  15. package/dist/bridge.js +217 -0
  16. package/dist/bridge.js.map +1 -0
  17. package/dist/cache.d.ts +27 -0
  18. package/dist/cache.d.ts.map +1 -0
  19. package/dist/cache.js +69 -0
  20. package/dist/cache.js.map +1 -0
  21. package/dist/cron.d.ts +22 -0
  22. package/dist/cron.d.ts.map +1 -0
  23. package/dist/cron.js +26 -0
  24. package/dist/cron.js.map +1 -0
  25. package/dist/db.d.ts +46 -0
  26. package/dist/db.d.ts.map +1 -0
  27. package/dist/db.js +158 -0
  28. package/dist/db.js.map +1 -0
  29. package/dist/error.d.ts +18 -0
  30. package/dist/error.d.ts.map +1 -0
  31. package/dist/error.js +30 -0
  32. package/dist/error.js.map +1 -0
  33. package/dist/index.d.ts +18 -0
  34. package/dist/index.d.ts.map +1 -0
  35. package/dist/index.js +18 -0
  36. package/dist/index.js.map +1 -0
  37. package/dist/islands.d.ts +16 -0
  38. package/dist/islands.d.ts.map +1 -0
  39. package/dist/islands.js +23 -0
  40. package/dist/islands.js.map +1 -0
  41. package/dist/middleware.d.ts +20 -0
  42. package/dist/middleware.d.ts.map +1 -0
  43. package/dist/middleware.js +26 -0
  44. package/dist/middleware.js.map +1 -0
  45. package/dist/nats.d.ts +46 -0
  46. package/dist/nats.d.ts.map +1 -0
  47. package/dist/nats.js +157 -0
  48. package/dist/nats.js.map +1 -0
  49. package/dist/pubsub.d.ts +27 -0
  50. package/dist/pubsub.d.ts.map +1 -0
  51. package/dist/pubsub.js +152 -0
  52. package/dist/pubsub.js.map +1 -0
  53. package/dist/queue.d.ts +39 -0
  54. package/dist/queue.d.ts.map +1 -0
  55. package/dist/queue.js +298 -0
  56. package/dist/queue.js.map +1 -0
  57. package/dist/rateLimit.d.ts +29 -0
  58. package/dist/rateLimit.d.ts.map +1 -0
  59. package/dist/rateLimit.js +70 -0
  60. package/dist/rateLimit.js.map +1 -0
  61. package/dist/registry.d.ts +75 -0
  62. package/dist/registry.d.ts.map +1 -0
  63. package/dist/registry.js +41 -0
  64. package/dist/registry.js.map +1 -0
  65. package/dist/secrets.d.ts +10 -0
  66. package/dist/secrets.d.ts.map +1 -0
  67. package/dist/secrets.js +35 -0
  68. package/dist/secrets.js.map +1 -0
  69. package/dist/ssr.d.ts +53 -0
  70. package/dist/ssr.d.ts.map +1 -0
  71. package/dist/ssr.js +39 -0
  72. package/dist/ssr.js.map +1 -0
  73. package/dist/storage.d.ts +28 -0
  74. package/dist/storage.d.ts.map +1 -0
  75. package/dist/storage.js +61 -0
  76. package/dist/storage.js.map +1 -0
  77. package/package.json +40 -0
  78. package/src/ai.ts +99 -0
  79. package/src/api.ts +56 -0
  80. package/src/auth.ts +16 -0
  81. package/src/bridge.ts +267 -0
  82. package/src/cache.ts +86 -0
  83. package/src/cron.ts +32 -0
  84. package/src/db.ts +211 -0
  85. package/src/error.ts +44 -0
  86. package/src/index.ts +17 -0
  87. package/src/islands.ts +28 -0
  88. package/src/middleware.ts +27 -0
  89. package/src/nats.ts +186 -0
  90. package/src/pubsub.ts +208 -0
  91. package/src/queue.ts +414 -0
  92. package/src/rateLimit.ts +89 -0
  93. package/src/registry.ts +98 -0
  94. package/src/secrets.ts +40 -0
  95. package/src/ssr.ts +58 -0
  96. package/src/storage.ts +79 -0
  97. package/tsconfig.json +17 -0
package/dist/queue.js ADDED
@@ -0,0 +1,298 @@
1
+ import { registry } from "./registry.js";
2
+ import { ensureConnected, getJetStream, getJetStreamManager, jsonCodec, consumerName, ensureQueueStream, ensureDLQStream, } from "./nats.js";
3
+ import { headers as natsHeaders, AckPolicy } from "nats";
4
+ const parseDuration = (dur) => {
5
+ const m = dur.match(/^(\d+)(s|m|h|d)$/);
6
+ if (!m)
7
+ return 0;
8
+ const mult = {
9
+ s: 1000,
10
+ m: 60000,
11
+ h: 3600000,
12
+ d: 86400000,
13
+ };
14
+ return parseInt(m[1]) * (mult[m[2]] ?? 1000);
15
+ };
16
+ /**
17
+ * Declare a job queue.
18
+ *
19
+ * ```ts
20
+ * export const EmailQueue = queue<{ to: string; subject: string; body: string }>(
21
+ * "email-queue",
22
+ * { concurrency: 10, retries: 3, retryDelay: "exponential", timeout: "30s", deadLetter: "email-dlq" }
23
+ * );
24
+ * ```
25
+ *
26
+ * Local dev uses NATS JetStream for persistent, durable queues.
27
+ * Falls back to in-memory if NATS is unavailable.
28
+ */
29
+ export function queue(name, config) {
30
+ const subject = `cooper.queue.${name}`;
31
+ const concurrency = config?.concurrency ?? 1;
32
+ const maxRetries = config?.retries ?? 0;
33
+ const timeoutMs = config?.timeout ? parseDuration(config.timeout) : 0;
34
+ const dlqName = config?.deadLetter;
35
+ // In-memory fallback state
36
+ const memJobs = [];
37
+ let memProcessing = false;
38
+ let workerHandler = null;
39
+ let failureHandler = null;
40
+ let consumerStarted = false;
41
+ // Track retry attempts per message (NATS redelivers original message on NAK)
42
+ const attemptTracker = new Map();
43
+ // In-memory fallback processor
44
+ const processMemQueue = async () => {
45
+ if (memProcessing || !workerHandler)
46
+ return;
47
+ memProcessing = true;
48
+ while (memJobs.length > 0) {
49
+ const batch = memJobs.splice(0, concurrency);
50
+ await Promise.allSettled(batch.map(async (job) => {
51
+ if (job.scheduledAt > Date.now()) {
52
+ memJobs.push(job);
53
+ return;
54
+ }
55
+ try {
56
+ await executeWithTimeout(workerHandler, job.data);
57
+ }
58
+ catch (err) {
59
+ job.attempts++;
60
+ if (job.attempts <= maxRetries) {
61
+ const delay = config?.retryDelay === "exponential"
62
+ ? Math.pow(2, job.attempts - 1) * 1000
63
+ : 1000;
64
+ job.scheduledAt = Date.now() + delay;
65
+ memJobs.push(job);
66
+ }
67
+ else if (failureHandler) {
68
+ await failureHandler(job.data, err);
69
+ }
70
+ }
71
+ }));
72
+ }
73
+ memProcessing = false;
74
+ };
75
+ async function executeWithTimeout(handler, data) {
76
+ if (!timeoutMs) {
77
+ return handler(data);
78
+ }
79
+ const result = await Promise.race([
80
+ handler(data),
81
+ new Promise((_, reject) => setTimeout(() => reject(new Error(`Job timed out after ${config.timeout}`)), timeoutMs)),
82
+ ]);
83
+ return result;
84
+ }
85
+ /**
86
+ * Start a JetStream consumer to process queue jobs.
87
+ */
88
+ async function startConsumer(workerName, handler, onFailure) {
89
+ if (consumerStarted)
90
+ return;
91
+ consumerStarted = true;
92
+ const js = getJetStream();
93
+ const jsm = getJetStreamManager();
94
+ if (!js || !jsm)
95
+ return;
96
+ const streamName = "QUEUE_" + name.replace(/[^a-zA-Z0-9_-]/g, "_").toUpperCase();
97
+ const durable = consumerName(workerName);
98
+ // Ensure consumer exists
99
+ try {
100
+ await jsm.consumers.info(streamName, durable);
101
+ }
102
+ catch {
103
+ await jsm.consumers.add(streamName, {
104
+ durable_name: durable,
105
+ ack_policy: AckPolicy.Explicit,
106
+ max_ack_pending: concurrency,
107
+ filter_subject: subject,
108
+ // Redeliver unacked messages after ack_wait
109
+ ack_wait: 30 * 1_000_000_000, // 30 seconds in nanos
110
+ });
111
+ }
112
+ const consumer = await js.consumers.get(streamName, durable);
113
+ // Process jobs in the background
114
+ (async () => {
115
+ try {
116
+ const messages = await consumer.consume();
117
+ for await (const msg of messages) {
118
+ let jobData;
119
+ try {
120
+ jobData = jsonCodec.decode(msg.data);
121
+ }
122
+ catch {
123
+ // Malformed message — ack to remove from queue
124
+ msg.ack();
125
+ continue;
126
+ }
127
+ // Track retries using sequence number — msg.seq is the stream seq
128
+ const seq = msg.seq;
129
+ const attempts = attemptTracker.get(seq) ?? 0;
130
+ try {
131
+ await executeWithTimeout(handler, jobData);
132
+ msg.ack();
133
+ attemptTracker.delete(seq);
134
+ }
135
+ catch (err) {
136
+ const nextAttempt = attempts + 1;
137
+ attemptTracker.set(seq, nextAttempt);
138
+ if (nextAttempt <= maxRetries) {
139
+ // NAK with backoff delay for retry
140
+ const delay = config?.retryDelay === "exponential"
141
+ ? Math.pow(2, nextAttempt - 1) * 1000
142
+ : 1000;
143
+ msg.nak(delay);
144
+ }
145
+ else {
146
+ // Max retries exceeded — move to DLQ if configured
147
+ if (dlqName) {
148
+ try {
149
+ await moveToDLQ(dlqName, name, jobData, err);
150
+ }
151
+ catch (dlqErr) {
152
+ console.error(`[cooper] Failed to move job to DLQ "${dlqName}":`, dlqErr);
153
+ }
154
+ }
155
+ if (onFailure) {
156
+ try {
157
+ await onFailure(jobData, err);
158
+ }
159
+ catch (failErr) {
160
+ console.error(`[cooper] onFailure handler for queue "${name}" threw:`, failErr);
161
+ }
162
+ }
163
+ // Ack to remove from main queue (it's in DLQ now)
164
+ msg.ack();
165
+ attemptTracker.delete(seq);
166
+ }
167
+ }
168
+ }
169
+ }
170
+ catch (err) {
171
+ consumerStarted = false;
172
+ if (!err.message?.includes("closed")) {
173
+ console.error(`[cooper] Queue consumer "${name}" stopped:`, err);
174
+ }
175
+ }
176
+ })();
177
+ }
178
+ const client = {
179
+ async enqueue(data, opts) {
180
+ const connected = await ensureConnected();
181
+ if (connected) {
182
+ const js = getJetStream();
183
+ if (js) {
184
+ await ensureQueueStream(name, { dedup: !!opts?.dedupeKey });
185
+ const h = natsHeaders();
186
+ // Dedup key
187
+ if (opts?.dedupeKey) {
188
+ h.set("Nats-Msg-Id", opts.dedupeKey);
189
+ }
190
+ // Priority as header (for visibility — JetStream doesn't natively prioritize)
191
+ if (opts?.priority && opts.priority !== "normal") {
192
+ h.set("Cooper-Priority", opts.priority);
193
+ }
194
+ // Initial attempt count
195
+ h.set("Cooper-Attempts", "0");
196
+ if (opts?.delay) {
197
+ // For delayed jobs: JetStream doesn't have native delay.
198
+ // We publish immediately but set a header; consumer-side can
199
+ // NAK with delay or we use a timer.
200
+ const delayMs = parseDuration(opts.delay);
201
+ h.set("Cooper-Delay-Until", String(Date.now() + delayMs));
202
+ }
203
+ await js.publish(subject, jsonCodec.encode(data), { headers: h });
204
+ return;
205
+ }
206
+ }
207
+ // Fallback: in-memory
208
+ const delay = opts?.delay ? parseDuration(opts.delay) : 0;
209
+ memJobs.push({
210
+ id: crypto.randomUUID(),
211
+ data,
212
+ attempts: 0,
213
+ priority: opts?.priority ?? "normal",
214
+ scheduledAt: Date.now() + delay,
215
+ });
216
+ const priorityOrder = {
217
+ high: 0,
218
+ normal: 1,
219
+ low: 2,
220
+ };
221
+ memJobs.sort((a, b) => (priorityOrder[a.priority] ?? 1) - (priorityOrder[b.priority] ?? 1));
222
+ setImmediate(() => processMemQueue());
223
+ },
224
+ worker(workerName, workerConfig) {
225
+ workerHandler = workerConfig.handler;
226
+ failureHandler = workerConfig.onFailure ?? null;
227
+ registry.registerQueue(name, {
228
+ name,
229
+ options: config ?? {},
230
+ worker: {
231
+ name: workerName,
232
+ handler: workerConfig.handler,
233
+ onFailure: workerConfig.onFailure,
234
+ },
235
+ });
236
+ // Start JetStream consumer
237
+ ensureConnected().then(async (connected) => {
238
+ if (connected) {
239
+ await ensureQueueStream(name);
240
+ if (dlqName)
241
+ await ensureDLQStream(dlqName);
242
+ startConsumer(workerName, workerConfig.handler, workerConfig.onFailure).catch((err) => {
243
+ console.error(`[cooper] Failed to start queue consumer "${name}":`, err);
244
+ });
245
+ }
246
+ });
247
+ // Kick in-memory fallback processing
248
+ setImmediate(() => processMemQueue());
249
+ return { _cooper_type: "queue_worker", queue: name, name: workerName };
250
+ },
251
+ async list() {
252
+ const connected = await ensureConnected();
253
+ if (connected) {
254
+ const jsm = getJetStreamManager();
255
+ if (jsm) {
256
+ const streamName = "QUEUE_" + name.replace(/[^a-zA-Z0-9_-]/g, "_").toUpperCase();
257
+ try {
258
+ const info = await jsm.streams.info(streamName);
259
+ // Return stream message count as approximation
260
+ // JetStream doesn't support listing individual messages directly
261
+ return Array.from({ length: Number(info.state.messages) }, (_, i) => ({
262
+ id: `js-${i}`,
263
+ data: {},
264
+ }));
265
+ }
266
+ catch {
267
+ return [];
268
+ }
269
+ }
270
+ }
271
+ return memJobs.map((j) => ({ id: j.id, data: j.data }));
272
+ },
273
+ async delete(id) {
274
+ // In-memory fallback
275
+ const idx = memJobs.findIndex((j) => j.id === id);
276
+ if (idx >= 0)
277
+ memJobs.splice(idx, 1);
278
+ },
279
+ };
280
+ registry.registerQueue(name, { name, options: config ?? {} });
281
+ return client;
282
+ }
283
+ /**
284
+ * Move a failed job to the dead-letter queue.
285
+ */
286
+ async function moveToDLQ(dlqName, sourceQueue, data, error) {
287
+ const js = getJetStream();
288
+ if (!js)
289
+ return;
290
+ await ensureDLQStream(dlqName);
291
+ const dlqSubject = `cooper.dlq.${dlqName}`;
292
+ const h = natsHeaders();
293
+ h.set("Cooper-Source-Queue", sourceQueue);
294
+ h.set("Cooper-Error", error.message.slice(0, 256));
295
+ h.set("Cooper-Failed-At", new Date().toISOString());
296
+ await js.publish(dlqSubject, jsonCodec.encode(data), { headers: h });
297
+ }
298
+ //# sourceMappingURL=queue.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queue.js","sourceRoot":"","sources":["../src/queue.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,29 @@
1
+ import type { MiddlewareFn } from "./registry.js";
2
+ export interface RateLimitConfig {
3
+ /** Time window — e.g. "1m", "10s", "1h" */
4
+ window: string;
5
+ /** Max requests allowed within the window */
6
+ max: number;
7
+ /** Function to derive the rate limit key from the request. Defaults to IP address. */
8
+ key?: (req: any) => string;
9
+ }
10
+ /**
11
+ * Create a rate limiting middleware.
12
+ *
13
+ * Can be used globally:
14
+ * ```ts
15
+ * cooper.use(rateLimit({ window: "1m", max: 100 }));
16
+ * ```
17
+ *
18
+ * Or per-route via middleware config:
19
+ * ```ts
20
+ * export default api({
21
+ * method: "POST",
22
+ * path: "/api/login",
23
+ * middleware: [rateLimit({ window: "1m", max: 5 })],
24
+ * handler: async (input) => { ... }
25
+ * });
26
+ * ```
27
+ */
28
+ export declare function rateLimit(config: RateLimitConfig): MiddlewareFn;
29
+ //# sourceMappingURL=rateLimit.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rateLimit.d.ts","sourceRoot":"","sources":["../src/rateLimit.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAElD,MAAM,WAAW,eAAe;IAC9B,2CAA2C;IAC3C,MAAM,EAAE,MAAM,CAAC;IACf,6CAA6C;IAC7C,GAAG,EAAE,MAAM,CAAC;IACZ,sFAAsF;IACtF,GAAG,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,MAAM,CAAC;CAC5B;AAwBD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,SAAS,CAAC,MAAM,EAAE,eAAe,GAAG,YAAY,CAoC/D"}
@@ -0,0 +1,70 @@
1
+ import { CooperError } from "./error.js";
2
+ function parseTTL(ttl) {
3
+ const match = ttl.match(/^(\d+)(s|m|h|d)$/);
4
+ if (!match)
5
+ return 60;
6
+ const [, num, unit] = match;
7
+ const multipliers = { s: 1, m: 60, h: 3600, d: 86400 };
8
+ return parseInt(num) * (multipliers[unit] ?? 60);
9
+ }
10
+ function parseWindowMs(window) {
11
+ return parseTTL(window) * 1000;
12
+ }
13
+ let redis = null;
14
+ async function ensureRedis() {
15
+ if (redis)
16
+ return redis;
17
+ const Redis = (await import("ioredis")).default;
18
+ const url = process.env.COOPER_VALKEY_URL ?? "redis://localhost:6379";
19
+ redis = new Redis(url);
20
+ return redis;
21
+ }
22
+ /**
23
+ * Create a rate limiting middleware.
24
+ *
25
+ * Can be used globally:
26
+ * ```ts
27
+ * cooper.use(rateLimit({ window: "1m", max: 100 }));
28
+ * ```
29
+ *
30
+ * Or per-route via middleware config:
31
+ * ```ts
32
+ * export default api({
33
+ * method: "POST",
34
+ * path: "/api/login",
35
+ * middleware: [rateLimit({ window: "1m", max: 5 })],
36
+ * handler: async (input) => { ... }
37
+ * });
38
+ * ```
39
+ */
40
+ export function rateLimit(config) {
41
+ const { window, max, key: keyFn } = config;
42
+ const ttlSec = parseTTL(window);
43
+ const windowMs = parseWindowMs(window);
44
+ return async (req, next) => {
45
+ const identifier = keyFn ? keyFn(req) : (req.ip ?? "unknown");
46
+ const redisKey = `cooper:rl:${identifier}:${Math.floor(Date.now() / windowMs)}`;
47
+ const r = await ensureRedis();
48
+ const count = await r.incr(redisKey);
49
+ // Set TTL on first increment
50
+ if (count === 1) {
51
+ await r.expire(redisKey, ttlSec);
52
+ }
53
+ if (count > max) {
54
+ // Calculate retry-after in seconds
55
+ const ttl = await r.ttl(redisKey);
56
+ const retryAfter = ttl > 0 ? ttl : ttlSec;
57
+ const error = new CooperError("RATE_LIMITED", `Rate limit exceeded. Try again in ${retryAfter}s.`);
58
+ error.retryAfter = retryAfter;
59
+ throw error;
60
+ }
61
+ // Attach rate limit headers info to request for downstream use
62
+ req._rateLimit = {
63
+ limit: max,
64
+ remaining: max - count,
65
+ reset: Math.ceil(Date.now() / 1000) + ttlSec,
66
+ };
67
+ return next(req);
68
+ };
69
+ }
70
+ //# sourceMappingURL=rateLimit.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rateLimit.js","sourceRoot":"","sources":["../src/rateLimit.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Internal registry — Cooper's Rust runtime communicates with JS workers
3
+ * by referencing registered handlers by name. This module tracks all
4
+ * declarations (routes, topics, crons, queues, etc.) so the bridge
5
+ * can look them up at runtime.
6
+ */
7
+ export interface RegisteredRoute {
8
+ method: string;
9
+ path: string;
10
+ auth: boolean;
11
+ stream?: "sse" | "websocket";
12
+ validate?: any;
13
+ middleware: MiddlewareFn[];
14
+ handler: Function;
15
+ exportName: string;
16
+ sourceFile: string;
17
+ }
18
+ export interface RegisteredTopic {
19
+ name: string;
20
+ subscribers: Map<string, {
21
+ handler: Function;
22
+ options: any;
23
+ }>;
24
+ }
25
+ export interface RegisteredCron {
26
+ name: string;
27
+ schedule: string;
28
+ handler: Function;
29
+ }
30
+ export interface RegisteredQueue {
31
+ name: string;
32
+ options: any;
33
+ worker?: {
34
+ name: string;
35
+ handler: Function;
36
+ onFailure?: Function;
37
+ };
38
+ }
39
+ export interface RegisteredDatabase {
40
+ name: string;
41
+ engine: string;
42
+ pool: any;
43
+ }
44
+ export interface RegisteredCache {
45
+ name: string;
46
+ options: any;
47
+ }
48
+ export interface RegisteredBucket {
49
+ name: string;
50
+ options: any;
51
+ }
52
+ export type MiddlewareFn = (req: any, next: (req: any) => Promise<any>) => Promise<any>;
53
+ export type AuthHandlerFn = (token: string) => Promise<Record<string, any>>;
54
+ declare class Registry {
55
+ routes: Map<string, RegisteredRoute>;
56
+ topics: Map<string, RegisteredTopic>;
57
+ crons: Map<string, RegisteredCron>;
58
+ queues: Map<string, RegisteredQueue>;
59
+ databases: Map<string, RegisteredDatabase>;
60
+ caches: Map<string, RegisteredCache>;
61
+ buckets: Map<string, RegisteredBucket>;
62
+ secrets: Map<string, () => Promise<string>>;
63
+ globalMiddleware: MiddlewareFn[];
64
+ authHandler: AuthHandlerFn | null;
65
+ registerRoute(key: string, route: RegisteredRoute): void;
66
+ registerTopic(name: string, topic: RegisteredTopic): void;
67
+ registerCron(name: string, cron: RegisteredCron): void;
68
+ registerQueue(name: string, queue: RegisteredQueue): void;
69
+ registerDatabase(name: string, db: RegisteredDatabase): void;
70
+ setAuthHandler(handler: AuthHandlerFn): void;
71
+ addGlobalMiddleware(mw: MiddlewareFn): void;
72
+ }
73
+ export declare const registry: Registry;
74
+ export {};
75
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Internal registry — Cooper's Rust runtime communicates with JS workers
3
+ * by referencing registered handlers by name. This module tracks all
4
+ * declarations (routes, topics, crons, queues, etc.) so the bridge
5
+ * can look them up at runtime.
6
+ */
7
+ class Registry {
8
+ routes = new Map();
9
+ topics = new Map();
10
+ crons = new Map();
11
+ queues = new Map();
12
+ databases = new Map();
13
+ caches = new Map();
14
+ buckets = new Map();
15
+ secrets = new Map();
16
+ globalMiddleware = [];
17
+ authHandler = null;
18
+ registerRoute(key, route) {
19
+ this.routes.set(key, route);
20
+ }
21
+ registerTopic(name, topic) {
22
+ this.topics.set(name, topic);
23
+ }
24
+ registerCron(name, cron) {
25
+ this.crons.set(name, cron);
26
+ }
27
+ registerQueue(name, queue) {
28
+ this.queues.set(name, queue);
29
+ }
30
+ registerDatabase(name, db) {
31
+ this.databases.set(name, db);
32
+ }
33
+ setAuthHandler(handler) {
34
+ this.authHandler = handler;
35
+ }
36
+ addGlobalMiddleware(mw) {
37
+ this.globalMiddleware.push(mw);
38
+ }
39
+ }
40
+ export const registry = new Registry();
41
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAmDH,MAAM,QAAQ;IACZ,MAAM,GAAiC,IAAI,GAAG,EAAE,CAAC;IACjD,MAAM,GAAiC,IAAI,GAAG,EAAE,CAAC;IACjD,KAAK,GAAgC,IAAI,GAAG,EAAE,CAAC;IAC/C,MAAM,GAAiC,IAAI,GAAG,EAAE,CAAC;IACjD,SAAS,GAAoC,IAAI,GAAG,EAAE,CAAC;IACvD,MAAM,GAAiC,IAAI,GAAG,EAAE,CAAC;IACjD,OAAO,GAAkC,IAAI,GAAG,EAAE,CAAC;IACnD,OAAO,GAAuC,IAAI,GAAG,EAAE,CAAC;IACxD,gBAAgB,GAAmB,EAAE,CAAC;IACtC,WAAW,GAAyB,IAAI,CAAC;IAEzC,aAAa,CAAC,GAAW,EAAE,KAAsB;QAC/C,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;IAC9B,CAAC;IAED,aAAa,CAAC,IAAY,EAAE,KAAsB;QAChD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED,YAAY,CAAC,IAAY,EAAE,IAAoB;QAC7C,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC;IAC7B,CAAC;IAED,aAAa,CAAC,IAAY,EAAE,KAAsB;QAChD,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IAC/B,CAAC;IAED,gBAAgB,CAAC,IAAY,EAAE,EAAsB;QACnD,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;IAC/B,CAAC;IAED,cAAc,CAAC,OAAsB;QACnC,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC;IAC7B,CAAC;IAED,mBAAmB,CAAC,EAAgB;QAClC,IAAI,CAAC,gBAAgB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjC,CAAC;CACF;AAED,MAAM,CAAC,MAAM,QAAQ,GAAG,IAAI,QAAQ,EAAE,CAAC"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Declare a secret — fetched at runtime, never in .env or source code.
3
+ *
4
+ * ```ts
5
+ * const stripeKey = secret("stripe-api-key");
6
+ * const client = new Stripe(await stripeKey());
7
+ * ```
8
+ */
9
+ export declare function secret(name: string): () => Promise<string>;
10
+ //# sourceMappingURL=secrets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.d.ts","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,OAAO,CAAC,MAAM,CAAC,CA+B1D"}
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Declare a secret — fetched at runtime, never in .env or source code.
3
+ *
4
+ * ```ts
5
+ * const stripeKey = secret("stripe-api-key");
6
+ * const client = new Stripe(await stripeKey());
7
+ * ```
8
+ */
9
+ export function secret(name) {
10
+ let cached = null;
11
+ const resolve = async () => {
12
+ if (cached !== null)
13
+ return cached;
14
+ // 1. Check env var (set by `cooper secrets set`)
15
+ const envKey = `COOPER_SECRET_${name.toUpperCase().replace(/-/g, "_")}`;
16
+ const envVal = process.env[envKey];
17
+ if (envVal) {
18
+ cached = envVal;
19
+ return envVal;
20
+ }
21
+ // 2. Check .cooper/secrets/<env>/<name>
22
+ const fs = require("node:fs");
23
+ const path = require("node:path");
24
+ const env = process.env.COOPER_ENV ?? "local";
25
+ const secretPath = path.join(".cooper", "secrets", env, name);
26
+ if (fs.existsSync(secretPath)) {
27
+ const val = fs.readFileSync(secretPath, "utf-8").trim();
28
+ cached = val;
29
+ return val;
30
+ }
31
+ throw new Error(`Secret "${name}" not found. Set it with: cooper secrets set ${name} --env ${env}`);
32
+ };
33
+ return resolve;
34
+ }
35
+ //# sourceMappingURL=secrets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"secrets.js","sourceRoot":"","sources":["../src/secrets.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,MAAM,UAAU,MAAM,CAAC,IAAY;IACjC,IAAI,MAAM,GAAkB,IAAI,CAAC;IAEjC,MAAM,OAAO,GAAG,KAAK,IAAqB,EAAE;QAC1C,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO,MAAM,CAAC;QAEnC,iDAAiD;QACjD,MAAM,MAAM,GAAG,iBAAiB,IAAI,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,CAAC;QACxE,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QACnC,IAAI,MAAM,EAAE,CAAC;YACX,MAAM,GAAG,MAAM,CAAC;YAChB,OAAO,MAAM,CAAC;QAChB,CAAC;QAED,wCAAwC;QACxC,MAAM,EAAE,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;QAC9B,MAAM,IAAI,GAAG,OAAO,CAAC,WAAW,CAAC,CAAC;QAClC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,IAAI,OAAO,CAAC;QAC9C,MAAM,UAAU,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,SAAS,EAAE,GAAG,EAAE,IAAI,CAAC,CAAC;QAC9D,IAAI,EAAE,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;YAC9B,MAAM,GAAG,GAAW,EAAE,CAAC,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;YAChE,MAAM,GAAG,GAAG,CAAC;YACb,OAAO,GAAG,CAAC;QACb,CAAC;QAED,MAAM,IAAI,KAAK,CACb,WAAW,IAAI,gDAAgD,IAAI,UAAU,GAAG,EAAE,CACnF,CAAC;IACJ,CAAC,CAAC;IAEF,OAAO,OAAO,CAAC;AACjB,CAAC"}
package/dist/ssr.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ export interface PageConfig {
2
+ cache?: string;
3
+ auth?: boolean;
4
+ }
5
+ export interface PageContext {
6
+ params: Record<string, string>;
7
+ req?: any;
8
+ data?: any;
9
+ }
10
+ /**
11
+ * Define a server-rendered page.
12
+ *
13
+ * ```ts
14
+ * export default page(async ({ params }) => {
15
+ * const { user } = await UsersService.getUser({ id: params.id });
16
+ * return <div><h1>{user.name}</h1></div>;
17
+ * });
18
+ * ```
19
+ */
20
+ export declare function page<T = any>(render: (ctx: PageContext) => Promise<T>): {
21
+ _cooper_type: "page";
22
+ render: typeof render;
23
+ };
24
+ /**
25
+ * Define a layout that wraps pages.
26
+ *
27
+ * ```ts
28
+ * export default layout(({ children }) => (
29
+ * <div><Nav /><main>{children}</main><Footer /></div>
30
+ * ));
31
+ * ```
32
+ */
33
+ export declare function layout<T = any>(render: (props: {
34
+ children: any;
35
+ }) => T): {
36
+ _cooper_type: "layout";
37
+ render: typeof render;
38
+ };
39
+ /**
40
+ * Define a page data loader — runs on the server, data passed to the page.
41
+ */
42
+ export declare function pageLoader<T>(loader: (ctx: PageContext) => Promise<T>): {
43
+ _cooper_type: "loader";
44
+ loader: typeof loader;
45
+ };
46
+ /**
47
+ * Suspense boundary for streaming SSR.
48
+ */
49
+ export declare function Suspense(_props: {
50
+ fallback: any;
51
+ children: any;
52
+ }): null;
53
+ //# sourceMappingURL=ssr.d.ts.map