@divizend/scratch-core 1.0.0 → 1.0.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.
@@ -2,7 +2,7 @@
2
2
  * HttpServer - Abstract HTTP server interface
3
3
  *
4
4
  * This interface defines the contract for HTTP server implementations.
5
- * Currently, only NativeHttpServer is implemented using Node's native http module.
5
+ * Currently, only ExpressHttpServer is implemented using Express.
6
6
  */
7
7
 
8
8
  import { ScratchEndpointDefinition, ScratchContext } from "../index";
@@ -21,12 +21,6 @@ export interface HttpServer {
21
21
  */
22
22
  stop(): Promise<void>;
23
23
 
24
- /**
25
- * Get the fetch handler for the server (for Bun/Cloudflare Workers)
26
- * @returns Fetch handler function
27
- */
28
- getFetchHandler(): (request: Request) => Promise<Response>;
29
-
30
24
  /**
31
25
  * Register static file serving
32
26
  * @param rootPath - Root path for static files
@@ -34,10 +28,13 @@ export interface HttpServer {
34
28
  registerStaticFiles(rootPath: string): void;
35
29
 
36
30
  /**
37
- * Load endpoints from a directory
38
- * @param directoryPath - Absolute path to directory containing endpoint files
31
+ * Load endpoints from a URL
32
+ * @param url - URL to load endpoints from (file:// or https://github.com)
33
+ * @returns Statistics about loaded and failed endpoints
39
34
  */
40
- loadEndpointsFromDirectory(directoryPath: string): Promise<void>;
35
+ loadEndpointsFromUrl(
36
+ url: string
37
+ ): Promise<{ loaded: number; failed: number; errors: string[] }>;
41
38
 
42
39
  /**
43
40
  * Get all endpoint definitions
@@ -85,6 +82,12 @@ export interface HttpServer {
85
82
  | undefined
86
83
  >;
87
84
 
85
+ /**
86
+ * Register the catch-all 404 handler
87
+ * This must be called AFTER all endpoints are loaded to ensure it's registered last
88
+ */
89
+ register404Handler(): void;
90
+
88
91
  /**
89
92
  * Register an endpoint from TypeScript source code (PUT operation - always overwrites)
90
93
  * @param source - TypeScript source code for the endpoint
@@ -0,0 +1,4 @@
1
+ export { loadEndpoints } from "./loadEndpoints";
2
+ export { loadEndpointsUI } from "./loadEndpointsUI";
3
+
4
+
@@ -0,0 +1,69 @@
1
+ import { ScratchEndpointDefinition } from "../../index";
2
+
3
+ export const loadEndpoints: ScratchEndpointDefinition = {
4
+ block: async () => ({
5
+ opcode: "loadEndpoints",
6
+ blockType: "command",
7
+ text: "load endpoints from URL [path] [jwt]",
8
+ schema: {
9
+ path: {
10
+ type: "string",
11
+ description:
12
+ "URL to load endpoints from (GitHub URL like https://github.com/owner/repo/tree/branch/path or file:// URL like file:///path/to/endpoints)",
13
+ },
14
+ jwt: {
15
+ type: "string",
16
+ description:
17
+ "JWT token for authentication (will be used for subsequent requests)",
18
+ },
19
+ },
20
+ }),
21
+ handler: async (context) => {
22
+ const { path } = context.inputs!;
23
+ if (!path || typeof path !== "string") {
24
+ throw new Error("path parameter is required and must be a string");
25
+ }
26
+
27
+ const endpointCountBefore =
28
+ context.universe!.httpServer.getAllEndpoints().length;
29
+ const result = await context.universe!.httpServer.loadEndpointsFromUrl(
30
+ path
31
+ );
32
+ const endpointCountAfter =
33
+ context.universe!.httpServer.getAllEndpoints().length;
34
+ const newEndpointsLoaded = endpointCountAfter - endpointCountBefore;
35
+
36
+ if (newEndpointsLoaded === 0 && result.failed > 0) {
37
+ throw new Error(
38
+ `Failed to load any endpoints from ${path}. ${
39
+ result.failed
40
+ } file(s) failed to load. Errors: ${result.errors
41
+ .slice(0, 5)
42
+ .join("; ")}${
43
+ result.errors.length > 5
44
+ ? ` (and ${result.errors.length - 5} more)`
45
+ : ""
46
+ }`
47
+ );
48
+ }
49
+
50
+ if (result.failed > 0) {
51
+ return {
52
+ success: true,
53
+ message: `Loaded ${newEndpointsLoaded} endpoint(s) from ${path} (${result.failed} file(s) failed)`,
54
+ totalEndpoints: endpointCountAfter,
55
+ loaded: result.loaded,
56
+ failed: result.failed,
57
+ errors: result.errors.slice(0, 10),
58
+ };
59
+ }
60
+
61
+ return {
62
+ success: true,
63
+ message: `Successfully loaded ${newEndpointsLoaded} endpoint(s) from ${path}`,
64
+ totalEndpoints: endpointCountAfter,
65
+ loaded: result.loaded,
66
+ failed: result.failed,
67
+ };
68
+ },
69
+ };
@@ -0,0 +1,43 @@
1
+ import { ScratchEndpointDefinition } from "../../index";
2
+
3
+ export const loadEndpointsUI: ScratchEndpointDefinition = {
4
+ block: async () => ({
5
+ opcode: "loadEndpointsUI",
6
+ blockType: "reporter",
7
+ text: "load endpoints UI [jwt]",
8
+ schema: {
9
+ jwt: {
10
+ type: "string",
11
+ description: "JWT token for authentication",
12
+ },
13
+ },
14
+ }),
15
+ handler: async (context) => {
16
+ const { jwt } = context.inputs!;
17
+ const html = `<!DOCTYPE html>
18
+ <html>
19
+ <head>
20
+ <title>Load Endpoints</title>
21
+ <style>
22
+ body { font-family: system-ui, -apple-system, sans-serif; max-width: 600px; margin: 50px auto; padding: 20px; }
23
+ input[type="text"] { width: 100%; padding: 10px; font-size: 16px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }
24
+ button { margin-top: 10px; padding: 10px 20px; font-size: 16px; background: #007bff; color: white; border: none; border-radius: 4px; cursor: pointer; }
25
+ button:hover { background: #0056b3; }
26
+ label { display: block; margin-bottom: 5px; font-weight: 500; }
27
+ </style>
28
+ </head>
29
+ <body>
30
+ <h1>Load Endpoints</h1>
31
+ <p>No endpoints loaded yet. Enter a URL to load endpoints from:</p>
32
+ <form method="POST" action="/loadEndpoints">
33
+ <input type="hidden" name="jwt" value="${jwt || ""}">
34
+ <label for="path">Endpoint URL:</label>
35
+ <input type="text" id="path" name="path" placeholder="file:///path/to/endpoints or https://github.com/owner/repo/tree/branch/path" required>
36
+ <button type="submit">Load Endpoints</button>
37
+ </form>
38
+ </body>
39
+ </html>`;
40
+ return html;
41
+ },
42
+ };
43
+
@@ -1,3 +1,3 @@
1
1
  export * from "./HttpServer";
2
- export * from "./NativeHttpServer";
2
+ export * from "./ExpressHttpServer";
3
3
 
package/index.ts CHANGED
@@ -15,10 +15,53 @@
15
15
  * @author Divizend GmbH
16
16
  */
17
17
 
18
+ console.log("[@divizend/scratch-core] Loading package/index.ts");
19
+ console.log("[@divizend/scratch-core] import.meta.url:", import.meta.url);
20
+ console.log(
21
+ "[@divizend/scratch-core] import.meta.dirname:",
22
+ import.meta.dirname
23
+ );
24
+
25
+ console.log("[@divizend/scratch-core] Exporting from ./basic");
18
26
  export * from "./basic";
27
+
28
+ console.log("[@divizend/scratch-core] Exporting from ./core");
19
29
  export * from "./core";
30
+
31
+ console.log("[@divizend/scratch-core] Exporting from ./gsuite");
20
32
  export * from "./gsuite";
33
+
34
+ console.log("[@divizend/scratch-core] Exporting from ./http-server");
21
35
  export * from "./http-server";
36
+
37
+ console.log("[@divizend/scratch-core] Exporting from ./resend");
22
38
  export * from "./resend";
39
+
40
+ console.log("[@divizend/scratch-core] Exporting from ./s2");
23
41
  export * from "./s2";
42
+
43
+ console.log("[@divizend/scratch-core] Exporting from ./queue");
24
44
  export * from "./queue";
45
+
46
+ // After exports, check what's actually exported
47
+ (async () => {
48
+ try {
49
+ const s2Module = await import("./s2");
50
+ console.log(
51
+ "[@divizend/scratch-core] s2 module keys:",
52
+ Object.keys(s2Module)
53
+ );
54
+ console.log(
55
+ "[@divizend/scratch-core] S2 class in s2 module:",
56
+ "S2" in s2Module
57
+ );
58
+ console.log(
59
+ "[@divizend/scratch-core] S2ReadResult in s2 module:",
60
+ "S2ReadResult" in s2Module
61
+ );
62
+ } catch (e) {
63
+ console.error("[@divizend/scratch-core] Error checking s2 module:", e);
64
+ }
65
+ })();
66
+
67
+ console.log("[@divizend/scratch-core] Finished loading package/index.ts");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@divizend/scratch-core",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Core library for Scratch endpoint system",
5
5
  "main": "index.ts",
6
6
  "type": "module",
@@ -16,6 +16,7 @@
16
16
  "dependencies": {
17
17
  "@s2-dev/streamstore": "^0.18.1",
18
18
  "cloudevents": "^10.0.0",
19
+ "express": "^5.2.1",
19
20
  "google-auth-library": "^10.5.0",
20
21
  "googleapis": "^167.0.0",
21
22
  "jose": "^5.6.0",
@@ -26,6 +27,7 @@
26
27
  },
27
28
  "devDependencies": {
28
29
  "@types/bun": "^1.3.3",
30
+ "@types/express": "^5.0.0",
29
31
  "@types/mustache": "^4.2.6",
30
32
  "@types/node": "^24.10.1",
31
33
  "@types/turndown": "^5.0.6",
package/s2/index.ts CHANGED
@@ -8,4 +8,23 @@
8
8
  * @author Divizend GmbH
9
9
  */
10
10
 
11
+ console.log("[@divizend/scratch-core/s2] Loading s2/index.ts");
12
+ console.log("[@divizend/scratch-core/s2] import.meta.url:", import.meta.url);
13
+
14
+ console.log("[@divizend/scratch-core/s2] Exporting from ./S2");
11
15
  export * from "./S2";
16
+
17
+ // After export, check what's actually exported
18
+ (async () => {
19
+ try {
20
+ console.log("[@divizend/scratch-core/s2] Importing from ./S2 to verify");
21
+ const s2File = await import("./S2");
22
+ console.log("[@divizend/scratch-core/s2] S2.ts module keys:", Object.keys(s2File));
23
+ console.log("[@divizend/scratch-core/s2] S2 class exists:", "S2" in s2File);
24
+ console.log("[@divizend/scratch-core/s2] S2ReadResult exists:", "S2ReadResult" in s2File);
25
+ console.log("[@divizend/scratch-core/s2] Successfully verified ./S2 exports");
26
+ } catch (e) {
27
+ console.error("[@divizend/scratch-core/s2] Error verifying ./S2:", e);
28
+ console.error("[@divizend/scratch-core/s2] Error stack:", e instanceof Error ? e.stack : String(e));
29
+ }
30
+ })();