cooper-stack 0.5.2
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/ai.d.ts +60 -0
- package/dist/ai.d.ts.map +1 -0
- package/dist/ai.js +66 -0
- package/dist/ai.js.map +1 -0
- package/dist/api.d.ts +31 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +40 -0
- package/dist/api.js.map +1 -0
- package/dist/auth.d.ts +13 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +16 -0
- package/dist/auth.js.map +1 -0
- package/dist/bridge.d.ts +15 -0
- package/dist/bridge.d.ts.map +1 -0
- package/dist/bridge.js +217 -0
- package/dist/bridge.js.map +1 -0
- package/dist/cache.d.ts +27 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +69 -0
- package/dist/cache.js.map +1 -0
- package/dist/cron.d.ts +22 -0
- package/dist/cron.d.ts.map +1 -0
- package/dist/cron.js +26 -0
- package/dist/cron.js.map +1 -0
- package/dist/db.d.ts +30 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +75 -0
- package/dist/db.js.map +1 -0
- package/dist/error.d.ts +16 -0
- package/dist/error.d.ts.map +1 -0
- package/dist/error.js +28 -0
- package/dist/error.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +16 -0
- package/dist/index.js.map +1 -0
- package/dist/islands.d.ts +16 -0
- package/dist/islands.d.ts.map +1 -0
- package/dist/islands.js +23 -0
- package/dist/islands.js.map +1 -0
- package/dist/middleware.d.ts +20 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +26 -0
- package/dist/middleware.js.map +1 -0
- package/dist/pubsub.d.ts +24 -0
- package/dist/pubsub.d.ts.map +1 -0
- package/dist/pubsub.js +48 -0
- package/dist/pubsub.js.map +1 -0
- package/dist/queue.d.ts +36 -0
- package/dist/queue.d.ts.map +1 -0
- package/dist/queue.js +100 -0
- package/dist/queue.js.map +1 -0
- package/dist/registry.d.ts +75 -0
- package/dist/registry.d.ts.map +1 -0
- package/dist/registry.js +41 -0
- package/dist/registry.js.map +1 -0
- package/dist/secrets.d.ts +10 -0
- package/dist/secrets.d.ts.map +1 -0
- package/dist/secrets.js +35 -0
- package/dist/secrets.js.map +1 -0
- package/dist/ssr.d.ts +53 -0
- package/dist/ssr.d.ts.map +1 -0
- package/dist/ssr.js +39 -0
- package/dist/ssr.js.map +1 -0
- package/dist/storage.d.ts +28 -0
- package/dist/storage.d.ts.map +1 -0
- package/dist/storage.js +61 -0
- package/dist/storage.js.map +1 -0
- package/package.json +38 -0
- package/src/ai.ts +99 -0
- package/src/api.ts +56 -0
- package/src/auth.ts +16 -0
- package/src/bridge.ts +267 -0
- package/src/cache.ts +86 -0
- package/src/cron.ts +32 -0
- package/src/db.ts +109 -0
- package/src/error.ts +42 -0
- package/src/index.ts +15 -0
- package/src/islands.ts +28 -0
- package/src/middleware.ts +27 -0
- package/src/pubsub.ts +69 -0
- package/src/queue.ts +133 -0
- package/src/registry.ts +98 -0
- package/src/secrets.ts +40 -0
- package/src/ssr.ts +58 -0
- package/src/storage.ts +79 -0
- package/tsconfig.json +17 -0
|
@@ -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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"}
|
package/dist/registry.js
ADDED
|
@@ -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"}
|
package/dist/secrets.js
ADDED
|
@@ -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
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssr.d.ts","sourceRoot":"","sources":["../src/ssr.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,UAAU;IACzB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,CAAC,EAAE,OAAO,CAAC;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,GAAG,CAAC,EAAE,GAAG,CAAC;IACV,IAAI,CAAC,EAAE,GAAG,CAAC;CACZ;AAED;;;;;;;;;GASG;AACH,wBAAgB,IAAI,CAAC,CAAC,GAAG,GAAG,EAC1B,MAAM,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,GACvC;IAAE,YAAY,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,OAAO,MAAM,CAAA;CAAE,CAEjD;AAED;;;;;;;;GAQG;AACH,wBAAgB,MAAM,CAAC,CAAC,GAAG,GAAG,EAC5B,MAAM,EAAE,CAAC,KAAK,EAAE;IAAE,QAAQ,EAAE,GAAG,CAAA;CAAE,KAAK,CAAC,GACtC;IAAE,YAAY,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,OAAO,MAAM,CAAA;CAAE,CAEnD;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAC1B,MAAM,EAAE,CAAC,GAAG,EAAE,WAAW,KAAK,OAAO,CAAC,CAAC,CAAC,GACvC;IAAE,YAAY,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,OAAO,MAAM,CAAA;CAAE,CAEnD;AAED;;GAEG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE;IAAE,QAAQ,EAAE,GAAG,CAAC;IAAC,QAAQ,EAAE,GAAG,CAAA;CAAE,QAGhE"}
|
package/dist/ssr.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Define a server-rendered page.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* export default page(async ({ params }) => {
|
|
6
|
+
* const { user } = await UsersService.getUser({ id: params.id });
|
|
7
|
+
* return <div><h1>{user.name}</h1></div>;
|
|
8
|
+
* });
|
|
9
|
+
* ```
|
|
10
|
+
*/
|
|
11
|
+
export function page(render) {
|
|
12
|
+
return { _cooper_type: "page", render };
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Define a layout that wraps pages.
|
|
16
|
+
*
|
|
17
|
+
* ```ts
|
|
18
|
+
* export default layout(({ children }) => (
|
|
19
|
+
* <div><Nav /><main>{children}</main><Footer /></div>
|
|
20
|
+
* ));
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
export function layout(render) {
|
|
24
|
+
return { _cooper_type: "layout", render };
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Define a page data loader — runs on the server, data passed to the page.
|
|
28
|
+
*/
|
|
29
|
+
export function pageLoader(loader) {
|
|
30
|
+
return { _cooper_type: "loader", loader };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Suspense boundary for streaming SSR.
|
|
34
|
+
*/
|
|
35
|
+
export function Suspense(_props) {
|
|
36
|
+
// Handled by the SSR renderer — this is a marker component
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=ssr.js.map
|
package/dist/ssr.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ssr.js","sourceRoot":"","sources":["../src/ssr.ts"],"names":[],"mappings":"AAWA;;;;;;;;;GASG;AACH,MAAM,UAAU,IAAI,CAClB,MAAwC;IAExC,OAAO,EAAE,YAAY,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAC1C,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,UAAU,MAAM,CACpB,MAAuC;IAEvC,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;AAC5C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CACxB,MAAwC;IAExC,OAAO,EAAE,YAAY,EAAE,QAAQ,EAAE,MAAM,EAAE,CAAC;AAC5C,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,QAAQ,CAAC,MAAwC;IAC/D,2DAA2D;IAC3D,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export interface BucketConfig {
|
|
2
|
+
public?: boolean;
|
|
3
|
+
}
|
|
4
|
+
export interface BucketClient {
|
|
5
|
+
upload(key: string, data: Buffer | Uint8Array, opts?: {
|
|
6
|
+
contentType?: string;
|
|
7
|
+
}): Promise<void>;
|
|
8
|
+
download(key: string): Promise<Buffer>;
|
|
9
|
+
signedUrl(key: string, opts?: {
|
|
10
|
+
ttl?: string;
|
|
11
|
+
}): Promise<string>;
|
|
12
|
+
delete(key: string): Promise<void>;
|
|
13
|
+
list(prefix?: string): Promise<{
|
|
14
|
+
key: string;
|
|
15
|
+
size: number;
|
|
16
|
+
lastModified: Date;
|
|
17
|
+
}[]>;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Declare an object storage bucket.
|
|
21
|
+
*
|
|
22
|
+
* ```ts
|
|
23
|
+
* export const avatars = bucket("avatars", { public: true });
|
|
24
|
+
* await avatars.upload("user-123/avatar.png", buffer, { contentType: "image/png" });
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare function bucket(name: string, config?: BucketConfig): BucketClient;
|
|
28
|
+
//# sourceMappingURL=storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.d.ts","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,UAAU,EAAE,IAAI,CAAC,EAAE;QAAE,WAAW,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/F,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACvC,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,GAAG,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACjE,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,IAAI,CAAA;KAAE,EAAE,CAAC,CAAC;CACrF;AAED;;;;;;;GAOG;AACH,wBAAgB,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,YAAY,GAAG,YAAY,CA0DxE"}
|
package/dist/storage.js
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declare an object storage bucket.
|
|
3
|
+
*
|
|
4
|
+
* ```ts
|
|
5
|
+
* export const avatars = bucket("avatars", { public: true });
|
|
6
|
+
* await avatars.upload("user-123/avatar.png", buffer, { contentType: "image/png" });
|
|
7
|
+
* ```
|
|
8
|
+
*/
|
|
9
|
+
export function bucket(name, config) {
|
|
10
|
+
const fs = require("node:fs");
|
|
11
|
+
const path = require("node:path");
|
|
12
|
+
// Local storage directory — in production this maps to S3/GCS/Azure Blob
|
|
13
|
+
const localDir = path.join(process.env.COOPER_STORAGE_DIR ?? ".cooper/storage", name);
|
|
14
|
+
return {
|
|
15
|
+
async upload(key, data, opts) {
|
|
16
|
+
const fullPath = path.join(localDir, key);
|
|
17
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
18
|
+
fs.writeFileSync(fullPath, data);
|
|
19
|
+
},
|
|
20
|
+
async download(key) {
|
|
21
|
+
const fullPath = path.join(localDir, key);
|
|
22
|
+
return fs.readFileSync(fullPath);
|
|
23
|
+
},
|
|
24
|
+
async signedUrl(key, opts) {
|
|
25
|
+
// In local dev, return a direct file path
|
|
26
|
+
// In production, generate a presigned S3/GCS URL
|
|
27
|
+
return `http://localhost:9400/storage/${name}/${key}`;
|
|
28
|
+
},
|
|
29
|
+
async delete(key) {
|
|
30
|
+
const fullPath = path.join(localDir, key);
|
|
31
|
+
if (fs.existsSync(fullPath))
|
|
32
|
+
fs.unlinkSync(fullPath);
|
|
33
|
+
},
|
|
34
|
+
async list(prefix) {
|
|
35
|
+
const dir = prefix ? path.join(localDir, prefix) : localDir;
|
|
36
|
+
if (!fs.existsSync(dir))
|
|
37
|
+
return [];
|
|
38
|
+
const entries = [];
|
|
39
|
+
const walk = (d, rel) => {
|
|
40
|
+
for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
|
|
41
|
+
const fullPath = path.join(d, entry.name);
|
|
42
|
+
const relPath = rel ? `${rel}/${entry.name}` : entry.name;
|
|
43
|
+
if (entry.isDirectory()) {
|
|
44
|
+
walk(fullPath, relPath);
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
const stat = fs.statSync(fullPath);
|
|
48
|
+
entries.push({
|
|
49
|
+
key: relPath,
|
|
50
|
+
size: stat.size,
|
|
51
|
+
lastModified: stat.mtime,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
walk(dir, "");
|
|
57
|
+
return entries;
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=storage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"storage.js","sourceRoot":"","sources":["../src/storage.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "cooper-stack",
|
|
3
|
+
"version": "0.5.2",
|
|
4
|
+
"description": "The backend framework for TypeScript",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": "./dist/index.js",
|
|
10
|
+
"./api": "./dist/api.js",
|
|
11
|
+
"./db": "./dist/db.js",
|
|
12
|
+
"./middleware": "./dist/middleware.js",
|
|
13
|
+
"./auth": "./dist/auth.js",
|
|
14
|
+
"./pubsub": "./dist/pubsub.js",
|
|
15
|
+
"./cron": "./dist/cron.js",
|
|
16
|
+
"./cache": "./dist/cache.js",
|
|
17
|
+
"./storage": "./dist/storage.js",
|
|
18
|
+
"./secrets": "./dist/secrets.js",
|
|
19
|
+
"./queue": "./dist/queue.js",
|
|
20
|
+
"./ssr": "./dist/ssr.js",
|
|
21
|
+
"./islands": "./dist/islands.js",
|
|
22
|
+
"./ai": "./dist/ai.js"
|
|
23
|
+
},
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc",
|
|
26
|
+
"dev": "tsc --watch"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"pg": "^8.13.0",
|
|
30
|
+
"mysql2": "^3.11.0",
|
|
31
|
+
"ioredis": "^5.4.0"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"typescript": "^5.7.0",
|
|
35
|
+
"@types/node": "^22.0.0",
|
|
36
|
+
"@types/pg": "^8.11.0"
|
|
37
|
+
}
|
|
38
|
+
}
|
package/src/ai.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
export interface VectorStoreConfig {
|
|
2
|
+
dimensions: number;
|
|
3
|
+
similarity?: "cosine" | "euclidean" | "dot";
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface VectorStoreClient {
|
|
7
|
+
upsert(id: string, vector: number[], metadata?: Record<string, any>): Promise<void>;
|
|
8
|
+
search(vector: number[], opts?: { topK?: number; filter?: Record<string, any> }): Promise<{ id: string; score: number; metadata?: any }[]>;
|
|
9
|
+
delete(id: string): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface LLMGatewayConfig {
|
|
13
|
+
primary: { provider: string; model: string };
|
|
14
|
+
fallback?: { provider: string; model: string };
|
|
15
|
+
budget?: { dailyLimit: string };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface LLMGatewayClient {
|
|
19
|
+
chat(messages: { role: string; content: string }[]): Promise<string>;
|
|
20
|
+
embed(text: string): Promise<number[]>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Declare a vector store.
|
|
25
|
+
*
|
|
26
|
+
* ```ts
|
|
27
|
+
* export const embeddings = vectorStore("product-embeddings", {
|
|
28
|
+
* dimensions: 1536,
|
|
29
|
+
* similarity: "cosine",
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export function vectorStore(name: string, config: VectorStoreConfig): VectorStoreClient {
|
|
34
|
+
// Local dev: pgvector extension on embedded Postgres
|
|
35
|
+
// Production: Pinecone, Weaviate, or pgvector on managed Postgres
|
|
36
|
+
const store = new Map<string, { vector: number[]; metadata?: any }>();
|
|
37
|
+
|
|
38
|
+
const cosineSimilarity = (a: number[], b: number[]): number => {
|
|
39
|
+
let dot = 0, magA = 0, magB = 0;
|
|
40
|
+
for (let i = 0; i < a.length; i++) {
|
|
41
|
+
dot += a[i] * b[i];
|
|
42
|
+
magA += a[i] * a[i];
|
|
43
|
+
magB += b[i] * b[i];
|
|
44
|
+
}
|
|
45
|
+
return dot / (Math.sqrt(magA) * Math.sqrt(magB));
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
return {
|
|
49
|
+
async upsert(id, vector, metadata) {
|
|
50
|
+
store.set(id, { vector, metadata });
|
|
51
|
+
},
|
|
52
|
+
|
|
53
|
+
async search(vector, opts) {
|
|
54
|
+
const topK = opts?.topK ?? 10;
|
|
55
|
+
const results: { id: string; score: number; metadata?: any }[] = [];
|
|
56
|
+
|
|
57
|
+
for (const [id, entry] of store) {
|
|
58
|
+
const score = cosineSimilarity(vector, entry.vector);
|
|
59
|
+
results.push({ id, score, metadata: entry.metadata });
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
results.sort((a, b) => b.score - a.score);
|
|
63
|
+
return results.slice(0, topK);
|
|
64
|
+
},
|
|
65
|
+
|
|
66
|
+
async delete(id) {
|
|
67
|
+
store.delete(id);
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Declare an LLM gateway with cost tracking and fallbacks.
|
|
74
|
+
*
|
|
75
|
+
* ```ts
|
|
76
|
+
* export const llm = llmGateway({
|
|
77
|
+
* primary: { provider: "openai", model: "gpt-4o" },
|
|
78
|
+
* fallback: { provider: "anthropic", model: "claude-sonnet-4-20250514" },
|
|
79
|
+
* budget: { dailyLimit: "$50" },
|
|
80
|
+
* });
|
|
81
|
+
* ```
|
|
82
|
+
*/
|
|
83
|
+
export function llmGateway(config: LLMGatewayConfig): LLMGatewayClient {
|
|
84
|
+
return {
|
|
85
|
+
async chat(messages) {
|
|
86
|
+
// Route to the configured provider
|
|
87
|
+
// Track cost, apply rate limits, handle fallback
|
|
88
|
+
throw new Error(
|
|
89
|
+
`LLM gateway not yet connected. Configure API keys via: cooper secrets set ${config.primary.provider}-api-key --env local`
|
|
90
|
+
);
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
async embed(text) {
|
|
94
|
+
throw new Error(
|
|
95
|
+
`LLM gateway not yet connected. Configure API keys via: cooper secrets set ${config.primary.provider}-api-key --env local`
|
|
96
|
+
);
|
|
97
|
+
},
|
|
98
|
+
};
|
|
99
|
+
}
|
package/src/api.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { registry, type MiddlewareFn } from "./registry.js";
|
|
2
|
+
import { CooperError } from "./error.js";
|
|
3
|
+
|
|
4
|
+
export interface ApiConfig {
|
|
5
|
+
method?: "GET" | "POST" | "PUT" | "PATCH" | "DELETE";
|
|
6
|
+
path: string;
|
|
7
|
+
auth?: boolean;
|
|
8
|
+
stream?: "sse" | "websocket";
|
|
9
|
+
validate?: any; // Zod schema
|
|
10
|
+
middleware?: MiddlewareFn[];
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Define an API endpoint.
|
|
15
|
+
*
|
|
16
|
+
* ```ts
|
|
17
|
+
* export const getUser = api(
|
|
18
|
+
* { method: "GET", path: "/users/:id", auth: true },
|
|
19
|
+
* async ({ id }, principal) => {
|
|
20
|
+
* const user = await db.queryRow("SELECT * FROM users WHERE id = $1", [id]);
|
|
21
|
+
* if (!user) throw new CooperError("NOT_FOUND", "User not found");
|
|
22
|
+
* return { user };
|
|
23
|
+
* }
|
|
24
|
+
* );
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export function api<TInput = any, TOutput = any>(
|
|
28
|
+
config: ApiConfig,
|
|
29
|
+
handler: (input: TInput, principal?: any) => Promise<TOutput>
|
|
30
|
+
): { _cooper_type: "api"; config: ApiConfig; handler: typeof handler } {
|
|
31
|
+
const method = config.method ?? "GET";
|
|
32
|
+
const key = `${method}:${config.path}`;
|
|
33
|
+
|
|
34
|
+
const descriptor = {
|
|
35
|
+
_cooper_type: "api" as const,
|
|
36
|
+
config,
|
|
37
|
+
handler,
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// Register for the bridge to find at runtime
|
|
41
|
+
registry.registerRoute(key, {
|
|
42
|
+
method,
|
|
43
|
+
path: config.path,
|
|
44
|
+
auth: config.auth ?? false,
|
|
45
|
+
stream: config.stream,
|
|
46
|
+
validate: config.validate,
|
|
47
|
+
middleware: config.middleware ?? [],
|
|
48
|
+
handler,
|
|
49
|
+
exportName: "", // filled in by bridge during module scan
|
|
50
|
+
sourceFile: "",
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return descriptor;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export { CooperError };
|
package/src/auth.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { registry, type AuthHandlerFn } from "./registry.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Register an auth handler that verifies tokens and returns a principal.
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* export const auth = authHandler(async (token) => {
|
|
8
|
+
* const payload = await verifyJWT(token);
|
|
9
|
+
* return { userId: payload.sub, role: payload.role };
|
|
10
|
+
* });
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export function authHandler(handler: AuthHandlerFn) {
|
|
14
|
+
registry.setAuthHandler(handler);
|
|
15
|
+
return handler;
|
|
16
|
+
}
|