miqro.js 0.1.2 → 0.1.4

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 qnton
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,7 +1,17 @@
1
1
  # Miqro
2
2
 
3
3
  A minimal, high-performance microservice engine built on [Bun](https://bun.sh/) and [Hono](https://hono.dev/).
4
- Designed to process webhooks and run scheduled cron jobs utilizing file-based workflow configurations.
4
+ Designed to process webhooks and run scheduled cron jobs utilizing file-based workflow configurations with first-class TypeScript support and Zod validation.
5
+
6
+ ## Key Features
7
+
8
+ - **High Performance:** Powered by Bun and Hono.
9
+ - **Developer Friendly:** Built-in CLI for scaffolding and development.
10
+ - **Zod Validation:** Automatic request validation for your webhooks.
11
+ - **Security First:** Simple API Key and Bearer Token authentication.
12
+ - **Scheduled Jobs:** Native support for cron-based workflows.
13
+ - **Middleware Support:** Easily extend the engine with Hono middleware.
14
+ - **Single File Builds:** Bundle your entire project into a single executable artifact.
5
15
 
6
16
  ## Quick Start
7
17
 
@@ -11,7 +21,7 @@ bunx miqro.js init
11
21
  bun add miqro.js
12
22
  ```
13
23
 
14
- This creates a `miqro.config.ts`, a `workflows/` directory, a basic `package.json`, and a `tsconfig.json`.
24
+ This creates a `miqro.config.ts`, a `workflows/` directory, and configures your environment.
15
25
 
16
26
  Start the development server with hot-reloading:
17
27
  ```bash
@@ -20,46 +30,57 @@ bun run dev
20
30
 
21
31
  ## Commands
22
32
 
23
- - `miqro.js init` (or `miqro init`): Scaffolds `miqro.config.ts` and a `workflows/` directory.
24
- - `miqro.js dev`: Starts the engine in development mode with hot-reloading (`bun --watch`).
25
- - `miqro.js start`: Starts the engine dynamically for production.
26
- - `miqro.js build`: Compiles your project and workflows into a single standalone file at `./dist/index.js`.
33
+ - `miqro init`: Scaffolds a new project.
34
+ - `miqro dev`: Starts the engine in development mode with hot-reloading.
35
+ - `miqro start`: Starts the engine for production.
36
+ - `miqro build`: Compiles your project into a single standalone file at `./dist/index.js`.
27
37
 
28
38
  ## Configuration
29
39
 
30
- The engine is configured via `miqro.config.ts` in your project root:
40
+ The engine is configured via `miqro.config.ts`:
31
41
 
32
42
  ```typescript
33
- // miqro.config.ts
34
43
  import type { MiqroConfig } from 'miqro.js';
35
44
 
36
45
  export default {
37
46
  port: 3000,
38
- workflowsDir: './workflows', // Directory containing workflows
47
+ workflowsDir: './workflows',
48
+ middleware: [
49
+ async (c, next) => {
50
+ console.log(`[${c.req.method}] ${c.req.url}`);
51
+ await next();
52
+ }
53
+ ]
39
54
  } satisfies MiqroConfig;
40
55
  ```
41
56
 
42
57
  ## Workflows
43
58
 
44
- Workflows belong in the directory specified by `workflowsDir`.
45
- Miqro automatically discovers `.ts` or `.js` module exports and registers them.
59
+ ### Webhook Workflow with Validation
46
60
 
47
- ### Webhook Workflow
48
-
49
- Webhooks respond to `POST /{id}`. Authentication supports `none`, `apiKey`, or `bearer`.
61
+ Webhooks respond to `POST /{id}`. Use **Zod** to validate your payloads and get full type safety in your execution logic.
50
62
 
51
63
  ```typescript
52
- // workflows/example-webhook.ts
53
- import type { Workflow } from 'miqro.js';
64
+ import { z } from "zod";
65
+ import type { Workflow } from "miqro.js";
54
66
 
55
67
  export default {
56
68
  config: {
57
69
  id: 'process-payment',
58
70
  name: 'Payment Webhook',
59
- auth: { type: 'apiKey', key: process.env.API_KEY || 'secret' }
71
+ auth: { type: 'apiKey', key: process.env.API_KEY || 'secret' },
72
+ schema: z.object({
73
+ amount: z.number().positive(),
74
+ currency: z.string().length(3),
75
+ customerEmail: z.string().email(),
76
+ })
60
77
  },
61
- execute: async (payload: any) => {
62
- console.log(`Received payload!`, payload);
78
+ execute: async (payload, context) => {
79
+ // payload is automatically typed based on the schema!
80
+ console.log(`Processing ${payload.amount} ${payload.currency} for ${payload.customerEmail}`);
81
+
82
+ // access request metadata via context
83
+ console.log(`User Agent: ${context.headers['user-agent']}`);
63
84
  }
64
85
  } satisfies Workflow;
65
86
  ```
@@ -69,18 +90,27 @@ export default {
69
90
  If a workflow config provides a `schedule` property (a valid Cron string), it will be executed automatically.
70
91
 
71
92
  ```typescript
72
- // workflows/example-cron.ts
73
93
  import type { Workflow } from 'miqro.js';
74
94
 
75
95
  export default {
76
96
  config: {
77
- id: 'hourly-sync',
78
- name: 'Sync Database',
79
- auth: { type: 'none' }, // Auth is required but ignored for cron tasks
80
- schedule: '0 * * * *' // Runs every hour
97
+ id: 'daily-cleanup',
98
+ name: 'Database Cleanup',
99
+ auth: { type: 'none' },
100
+ schedule: '0 0 * * *' // Runs every night at midnight
81
101
  },
82
- execute: async () => {
83
- console.log(`Running background sync...`);
102
+ execute: async (payload, context) => {
103
+ console.log(`Running scheduled cleanup for ${context.name}...`);
84
104
  }
85
105
  } satisfies Workflow;
86
- ```
106
+ ```
107
+
108
+ ## Execution Context
109
+
110
+ The `execute` function receives a `MiqroContext` object providing access to:
111
+
112
+ - `workflowId`: The ID of the current workflow.
113
+ - `name`: The display name of the workflow.
114
+ - `params`: Route parameters.
115
+ - `query`: URL query parameters.
116
+ - `headers`: HTTP Request headers.
package/dist/cli.js CHANGED
@@ -2939,7 +2939,14 @@ async function loadWorkflows(workflowsDir) {
2939
2939
  import_node_cron.default.schedule(workflow.config.schedule, () => {
2940
2940
  console.log(`[Cron] Executing scheduled workflow: ${workflow.config.id}`);
2941
2941
  const payload = { source: "cron", timestamp: Date.now() };
2942
- workflow.execute(payload);
2942
+ const context = {
2943
+ workflowId: workflow.config.id,
2944
+ name: workflow.config.name,
2945
+ params: {},
2946
+ query: {},
2947
+ headers: {}
2948
+ };
2949
+ workflow.execute(payload, context);
2943
2950
  });
2944
2951
  console.log(`[Miqro] Scheduled workflow '${workflow.config.id}' with cron: '${workflow.config.schedule}'`);
2945
2952
  }
@@ -2961,7 +2968,14 @@ async function startMiqroCore(config, staticWorkflows) {
2961
2968
  import_node_cron.default.schedule(workflow.config.schedule, () => {
2962
2969
  console.log(`[Cron] Executing scheduled workflow: ${workflow.config.id}`);
2963
2970
  const payload = { source: "cron", timestamp: Date.now() };
2964
- workflow.execute(payload);
2971
+ const context = {
2972
+ workflowId: workflow.config.id,
2973
+ name: workflow.config.name,
2974
+ params: {},
2975
+ query: {},
2976
+ headers: {}
2977
+ };
2978
+ workflow.execute(payload, context);
2965
2979
  });
2966
2980
  console.log(`[Miqro] Scheduled workflow '${workflow.config.id}' with cron: '${workflow.config.schedule}'`);
2967
2981
  }
@@ -2969,6 +2983,11 @@ async function startMiqroCore(config, staticWorkflows) {
2969
2983
  }
2970
2984
  const app = new Hono2;
2971
2985
  app.use("*", logger());
2986
+ if (config.middleware) {
2987
+ for (const mw of config.middleware) {
2988
+ app.use("*", mw);
2989
+ }
2990
+ }
2972
2991
  app.get("/health", (c) => c.json({
2973
2992
  status: "ok",
2974
2993
  uptime: process.uptime(),
@@ -2976,34 +2995,45 @@ async function startMiqroCore(config, staticWorkflows) {
2976
2995
  }));
2977
2996
  app.post("/:workflowId", async (c) => {
2978
2997
  try {
2979
- const payload = await c.req.json();
2980
2998
  const workflowId = c.req.param("workflowId");
2981
- if (workflowId) {
2982
- const workflow = workflows[workflowId];
2983
- if (!workflow) {
2984
- return c.json({ error: `Workflow '${workflowId}' not found` }, 404);
2999
+ const workflow = workflows[workflowId];
3000
+ if (!workflow) {
3001
+ return c.json({ error: `Workflow '${workflowId}' not found` }, 404);
3002
+ }
3003
+ const authHeader = c.req.header("Authorization");
3004
+ const { auth } = workflow.config;
3005
+ if (auth.type === "apiKey") {
3006
+ const apiKey = c.req.header("x-api-key") || c.req.query("apiKey");
3007
+ if (apiKey !== auth.key) {
3008
+ return c.json({ error: "Unauthorized: Invalid API Key" }, 401);
2985
3009
  }
2986
- const authHeader = c.req.header("Authorization");
2987
- const { auth } = workflow.config;
2988
- if (auth.type === "apiKey") {
2989
- const apiKey = c.req.header("x-api-key") || c.req.query("apiKey");
2990
- if (apiKey !== auth.key) {
2991
- return c.json({ error: "Unauthorized: Invalid API Key" }, 401);
2992
- }
2993
- } else if (auth.type === "bearer") {
2994
- if (!authHeader || !authHeader.startsWith(`Bearer ${auth.token}`)) {
2995
- return c.json({ error: "Unauthorized: Invalid Bearer token" }, 401);
2996
- }
3010
+ } else if (auth.type === "bearer") {
3011
+ if (!authHeader || !authHeader.startsWith(`Bearer ${auth.token}`)) {
3012
+ return c.json({ error: "Unauthorized: Invalid Bearer token" }, 401);
2997
3013
  }
2998
- await workflow.execute(payload);
2999
- return c.json({
3000
- status: "success",
3001
- message: `Workflow '${workflowId}' executed`
3002
- });
3003
3014
  }
3015
+ let payload = await c.req.json();
3016
+ if (workflow.config.schema) {
3017
+ const result = workflow.config.schema.safeParse(payload);
3018
+ if (!result.success) {
3019
+ return c.json({
3020
+ error: "Validation Failed",
3021
+ details: result.error.format()
3022
+ }, 400);
3023
+ }
3024
+ payload = result.data;
3025
+ }
3026
+ const context = {
3027
+ workflowId: workflow.config.id,
3028
+ name: workflow.config.name,
3029
+ params: c.req.param(),
3030
+ query: c.req.query(),
3031
+ headers: c.req.header()
3032
+ };
3033
+ await workflow.execute(payload, context);
3004
3034
  return c.json({
3005
3035
  status: "success",
3006
- message: "Payload received but no workflow triggered"
3036
+ message: `Workflow '${workflowId}' executed`
3007
3037
  });
3008
3038
  } catch (error) {
3009
3039
  console.error("[Miqro Webhook Error]", error);
@@ -3061,18 +3091,25 @@ export default {
3061
3091
  }
3062
3092
  const sampleWorkflowPath = resolve(workflowsPath, "sample.ts");
3063
3093
  if (!existsSync(sampleWorkflowPath)) {
3064
- const sampleWorkflow = `import type { Workflow } from "miqro.js";
3094
+ const sampleWorkflow = `import { z } from "zod";
3095
+ import type { Workflow } from "miqro.js";
3096
+
3097
+ const schema = z.object({
3098
+ message: z.string().min(1),
3099
+ });
3065
3100
 
3066
3101
  export default {
3067
3102
  config: {
3068
3103
  id: "hello-world",
3069
3104
  name: "Hello World Workflow",
3070
3105
  auth: { type: "none" },
3106
+ schema,
3071
3107
  },
3072
- execute: async (payload: any) => {
3073
- console.log("Hello from Miqro!", payload);
3108
+ execute: async (payload, context) => {
3109
+ console.log("Hello from Miqro!", payload.message);
3110
+ console.log("Context workflow ID:", context.workflowId);
3074
3111
  },
3075
- } satisfies Workflow;
3112
+ } satisfies Workflow<z.infer<typeof schema>>;
3076
3113
  `;
3077
3114
  await writeFile(sampleWorkflowPath, sampleWorkflow);
3078
3115
  }
@@ -3081,6 +3118,13 @@ export default {
3081
3118
  const pkgContent = {
3082
3119
  name: "miqro-project",
3083
3120
  type: "module",
3121
+ dependencies: {
3122
+ "miqro.js": "^0.1.3",
3123
+ zod: "^3.24.1"
3124
+ },
3125
+ devDependencies: {
3126
+ "@types/bun": "latest"
3127
+ },
3084
3128
  scripts: {
3085
3129
  dev: "miqro dev",
3086
3130
  start: "miqro start",
@@ -3102,7 +3146,8 @@ export default {
3102
3146
  noEmit: true,
3103
3147
  strict: true,
3104
3148
  skipLibCheck: true,
3105
- allowSyntheticDefaultImports: true
3149
+ allowSyntheticDefaultImports: true,
3150
+ types: ["bun-types"]
3106
3151
  }
3107
3152
  };
3108
3153
  await writeFile(tsconfigPath, JSON.stringify(tsconfigContent, null, 2));
@@ -3110,7 +3155,7 @@ export default {
3110
3155
  console.log("\u2705 Created miqro.config.ts and workflows directory with a sample.");
3111
3156
  console.log(`
3112
3157
  \uD83D\uDC49 Next steps:`);
3113
- console.log(" 1. Run: bun add miqro.js");
3158
+ console.log(" 1. Run: bun install");
3114
3159
  console.log(" 2. Run: bun run dev");
3115
3160
  process.exit(0);
3116
3161
  }
@@ -3134,7 +3179,8 @@ export default {
3134
3179
  console.log("\uD83D\uDD04 Starting Miqro in DEV mode (hot-reloading enabled)...");
3135
3180
  Bun.spawn(["bun", "--watch", Bun.argv[1], "start"], {
3136
3181
  stdout: "inherit",
3137
- stderr: "inherit"
3182
+ stderr: "inherit",
3183
+ cwd: process.cwd()
3138
3184
  });
3139
3185
  } else if (command === "start") {
3140
3186
  console.log("\uD83D\uDE80 Starting Miqro...");
package/dist/index.d.ts CHANGED
@@ -14,4 +14,4 @@ export declare function startMiqro(config: MiqroConfig): Promise<{
14
14
  port: string | number;
15
15
  fetch: (request: Request, Env?: unknown, executionCtx?: import("hono").ExecutionContext) => Response | Promise<Response>;
16
16
  }>;
17
- export type { AuthConfig, Workflow, WorkflowConfig } from "./types";
17
+ export type { AuthConfig, MiqroConfig, Workflow, WorkflowConfig, } from "./types";
package/dist/index.js CHANGED
@@ -2938,7 +2938,14 @@ async function loadWorkflows(workflowsDir) {
2938
2938
  import_node_cron.default.schedule(workflow.config.schedule, () => {
2939
2939
  console.log(`[Cron] Executing scheduled workflow: ${workflow.config.id}`);
2940
2940
  const payload = { source: "cron", timestamp: Date.now() };
2941
- workflow.execute(payload);
2941
+ const context = {
2942
+ workflowId: workflow.config.id,
2943
+ name: workflow.config.name,
2944
+ params: {},
2945
+ query: {},
2946
+ headers: {}
2947
+ };
2948
+ workflow.execute(payload, context);
2942
2949
  });
2943
2950
  console.log(`[Miqro] Scheduled workflow '${workflow.config.id}' with cron: '${workflow.config.schedule}'`);
2944
2951
  }
@@ -2960,7 +2967,14 @@ async function startMiqroCore(config, staticWorkflows) {
2960
2967
  import_node_cron.default.schedule(workflow.config.schedule, () => {
2961
2968
  console.log(`[Cron] Executing scheduled workflow: ${workflow.config.id}`);
2962
2969
  const payload = { source: "cron", timestamp: Date.now() };
2963
- workflow.execute(payload);
2970
+ const context = {
2971
+ workflowId: workflow.config.id,
2972
+ name: workflow.config.name,
2973
+ params: {},
2974
+ query: {},
2975
+ headers: {}
2976
+ };
2977
+ workflow.execute(payload, context);
2964
2978
  });
2965
2979
  console.log(`[Miqro] Scheduled workflow '${workflow.config.id}' with cron: '${workflow.config.schedule}'`);
2966
2980
  }
@@ -2968,6 +2982,11 @@ async function startMiqroCore(config, staticWorkflows) {
2968
2982
  }
2969
2983
  const app = new Hono2;
2970
2984
  app.use("*", logger());
2985
+ if (config.middleware) {
2986
+ for (const mw of config.middleware) {
2987
+ app.use("*", mw);
2988
+ }
2989
+ }
2971
2990
  app.get("/health", (c) => c.json({
2972
2991
  status: "ok",
2973
2992
  uptime: process.uptime(),
@@ -2975,34 +2994,45 @@ async function startMiqroCore(config, staticWorkflows) {
2975
2994
  }));
2976
2995
  app.post("/:workflowId", async (c) => {
2977
2996
  try {
2978
- const payload = await c.req.json();
2979
2997
  const workflowId = c.req.param("workflowId");
2980
- if (workflowId) {
2981
- const workflow = workflows[workflowId];
2982
- if (!workflow) {
2983
- return c.json({ error: `Workflow '${workflowId}' not found` }, 404);
2998
+ const workflow = workflows[workflowId];
2999
+ if (!workflow) {
3000
+ return c.json({ error: `Workflow '${workflowId}' not found` }, 404);
3001
+ }
3002
+ const authHeader = c.req.header("Authorization");
3003
+ const { auth } = workflow.config;
3004
+ if (auth.type === "apiKey") {
3005
+ const apiKey = c.req.header("x-api-key") || c.req.query("apiKey");
3006
+ if (apiKey !== auth.key) {
3007
+ return c.json({ error: "Unauthorized: Invalid API Key" }, 401);
2984
3008
  }
2985
- const authHeader = c.req.header("Authorization");
2986
- const { auth } = workflow.config;
2987
- if (auth.type === "apiKey") {
2988
- const apiKey = c.req.header("x-api-key") || c.req.query("apiKey");
2989
- if (apiKey !== auth.key) {
2990
- return c.json({ error: "Unauthorized: Invalid API Key" }, 401);
2991
- }
2992
- } else if (auth.type === "bearer") {
2993
- if (!authHeader || !authHeader.startsWith(`Bearer ${auth.token}`)) {
2994
- return c.json({ error: "Unauthorized: Invalid Bearer token" }, 401);
2995
- }
3009
+ } else if (auth.type === "bearer") {
3010
+ if (!authHeader || !authHeader.startsWith(`Bearer ${auth.token}`)) {
3011
+ return c.json({ error: "Unauthorized: Invalid Bearer token" }, 401);
2996
3012
  }
2997
- await workflow.execute(payload);
2998
- return c.json({
2999
- status: "success",
3000
- message: `Workflow '${workflowId}' executed`
3001
- });
3002
3013
  }
3014
+ let payload = await c.req.json();
3015
+ if (workflow.config.schema) {
3016
+ const result = workflow.config.schema.safeParse(payload);
3017
+ if (!result.success) {
3018
+ return c.json({
3019
+ error: "Validation Failed",
3020
+ details: result.error.format()
3021
+ }, 400);
3022
+ }
3023
+ payload = result.data;
3024
+ }
3025
+ const context = {
3026
+ workflowId: workflow.config.id,
3027
+ name: workflow.config.name,
3028
+ params: c.req.param(),
3029
+ query: c.req.query(),
3030
+ headers: c.req.header()
3031
+ };
3032
+ await workflow.execute(payload, context);
3003
3033
  return c.json({
3004
3034
  status: "success",
3005
- message: "Payload received but no workflow triggered"
3035
+ message: `Workflow '${workflowId}' executed`
3006
3036
  });
3007
3037
  } catch (error) {
3008
3038
  console.error("[Miqro Webhook Error]", error);
package/dist/types.d.ts CHANGED
@@ -1,3 +1,5 @@
1
+ import type { MiddlewareHandler } from "hono";
2
+ import type { z } from "zod";
1
3
  export type AuthConfig = {
2
4
  type: "none";
3
5
  } | {
@@ -7,18 +9,27 @@ export type AuthConfig = {
7
9
  type: "bearer";
8
10
  token: string;
9
11
  };
10
- export interface WorkflowConfig {
12
+ export interface MiqroContext {
13
+ workflowId: string;
14
+ name: string;
15
+ params: Record<string, string>;
16
+ query: Record<string, string | string[]>;
17
+ headers: Record<string, string>;
18
+ }
19
+ export interface WorkflowConfig<T = unknown> {
11
20
  id: string;
12
21
  name: string;
13
22
  description?: string;
14
23
  auth: AuthConfig;
15
24
  schedule?: string;
25
+ schema?: z.ZodType<T>;
16
26
  }
17
- export interface Workflow {
18
- config: WorkflowConfig;
19
- execute: (payload: unknown) => Promise<void> | void;
27
+ export interface Workflow<T = unknown> {
28
+ config: WorkflowConfig<T>;
29
+ execute: (payload: T, context: MiqroContext) => Promise<void> | void;
20
30
  }
21
31
  export interface MiqroConfig {
22
32
  workflowsDir: string;
23
33
  port?: number;
34
+ middleware?: MiddlewareHandler[];
24
35
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "miqro.js",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Minimal, high-performance microservice engine.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -30,6 +30,7 @@
30
30
  },
31
31
  "dependencies": {
32
32
  "hono": "^4.12.2",
33
- "node-cron": "^4.2.1"
33
+ "node-cron": "^4.2.1",
34
+ "zod": "^4.3.6"
34
35
  }
35
36
  }