flowli 0.2.1 → 0.2.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.
Files changed (3) hide show
  1. package/README.md +128 -168
  2. package/jsr.json +11 -1
  3. package/package.json +4 -3
package/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  **Typed jobs for modern TypeScript backends.**
4
4
 
5
+ [npm](https://www.npmjs.com/package/flowli) · [JSR](https://jsr.io/@alialnaghmoush/flowli) · [GitHub](https://github.com/alialnaghmoush/flowli)
6
+
5
7
  Flowli is a jobs runtime with a code-first API, first-class execution strategies, runtime-scoped context injection, and pluggable Redis drivers.
6
8
 
7
9
  Define jobs once. Run them anywhere.
@@ -9,6 +11,7 @@ Define jobs once. Run them anywhere.
9
11
  ## Navigate
10
12
 
11
13
  - [Why Flowli](#why-flowli)
14
+ - [Compare](#compare)
12
15
  - [What It Feels Like](#what-it-feels-like)
13
16
  - [The Core Idea](#the-core-idea)
14
17
  - [Primary Authoring Path](#primary-authoring-path)
@@ -46,6 +49,30 @@ Flowli is built around a different model:
46
49
 
47
50
  It is a typed runtime for background and deferred execution with a code-first, framework-agnostic design.
48
51
 
52
+ ## Compare
53
+
54
+ Choose Flowli when you want:
55
+
56
+ - application-first jobs authored in code, not in a dashboard
57
+ - a single runtime that supports both direct execution and persisted async work
58
+ - typed `context` injection without framework lock-in
59
+ - pluggable Redis clients behind a small API surface
60
+
61
+ Flowli vs BullMQ:
62
+
63
+ - Flowli centers the typed job-definition experience; BullMQ centers queue primitives and worker infrastructure
64
+ - Flowli makes `run()` a first-class in-process path; BullMQ is primarily queue-first
65
+
66
+ Flowli vs Trigger.dev:
67
+
68
+ - Flowli stays library-first and infrastructure-light
69
+ - Trigger.dev is stronger when you want a hosted platform, dashboard, and workflow operations out of the box
70
+
71
+ Flowli vs Inngest:
72
+
73
+ - Flowli is better suited when you want application-local jobs and direct runtime wiring
74
+ - Inngest is stronger when you want event-first workflows across services
75
+
49
76
  ```mermaid
50
77
  flowchart LR
51
78
  App["App Code<br/>Routes, Services, Scripts, Tests"] --> Runtime["defineJobs()<br/>Flowli Runtime"]
@@ -66,93 +93,9 @@ flowchart LR
66
93
  ## What It Feels Like
67
94
 
68
95
  ```ts
69
- // src/flowli/jobs/create-audit-log.ts
70
- import * as v from "valibot";
71
- import { job } from "flowli";
72
- import type { AppContext } from "..";
73
-
74
- export const auditLogSchema = v.object({
75
- entityType: v.string(),
76
- entityId: v.string(),
77
- action: v.string(),
78
- message: v.string(),
79
- });
80
-
81
- export const auditLogMeta = v.object({
82
- requestId: v.string(),
83
- actorId: v.optional(v.string()),
84
- });
85
-
86
- export const createAuditLog = job.withContext<AppContext>()(
87
- "create_audit_log",
88
- {
89
- input: auditLogSchema,
90
- meta: auditLogMeta,
91
- handler: async ({ input, ctx, meta }) => {
92
- await ctx.db.insert(ctx.schema.auditLogs).values({
93
- entityType: input.entityType,
94
- entityId: input.entityId,
95
- action: input.action,
96
- message: input.message,
97
- requestId: meta?.requestId,
98
- actorId: meta?.actorId ?? null,
99
- });
100
-
101
- ctx.logger.info({
102
- job: "create_audit_log",
103
- requestId: meta?.requestId,
104
- entityId: input.entityId,
105
- });
106
- },
107
- },
108
- );
109
- ```
110
-
111
- ```ts
112
- // src/flowli/jobs/send-notification-email.ts
113
96
  import * as v from "valibot";
114
- import { job } from "flowli";
115
- import type { AppContext } from "..";
116
-
117
- export const notificationEmailSchema = v.object({
118
- email: v.string(),
119
- subject: v.string(),
120
- message: v.string(),
121
- });
122
-
123
- export const sendNotificationEmail = job.withContext<AppContext>()(
124
- "send_notification_email",
125
- {
126
- input: notificationEmailSchema,
127
- handler: async ({ input, ctx }) => {
128
- await ctx.mailer.send({
129
- to: input.email,
130
- subject: input.subject,
131
- text: input.message,
132
- });
133
- },
134
- },
135
- );
136
- ```
137
-
138
- ```ts
139
- // src/flowli/jobs/index.ts
140
- export * from "./create-audit-log";
141
- export * from "./send-notification-email";
142
- ```
143
-
144
- ```ts
145
- // src/flowli/index.ts
146
97
  import { defineJobs } from "flowli";
147
98
  import { ioredisDriver } from "flowli/ioredis";
148
- import * as jobs from "./jobs";
149
-
150
- export type AppContext = {
151
- db: typeof db;
152
- schema: typeof schema;
153
- logger: typeof logger;
154
- mailer: typeof mailer;
155
- };
156
99
 
157
100
  export const flowli = defineJobs({
158
101
  driver: ioredisDriver({
@@ -165,7 +108,59 @@ export const flowli = defineJobs({
165
108
  logger,
166
109
  mailer,
167
110
  }),
168
- jobs,
111
+ jobs: ({ job }) => {
112
+ const auditLogSchema = v.object({
113
+ entityType: v.string(),
114
+ entityId: v.string(),
115
+ action: v.string(),
116
+ message: v.string(),
117
+ });
118
+
119
+ const auditLogMetaSchema = v.object({
120
+ requestId: v.string(),
121
+ actorId: v.optional(v.string()),
122
+ });
123
+
124
+ const notificationEmailSchema = v.object({
125
+ email: v.string(),
126
+ subject: v.string(),
127
+ message: v.string(),
128
+ });
129
+
130
+ return {
131
+ createAuditLog: job("create_audit_log", {
132
+ input: auditLogSchema,
133
+ meta: auditLogMetaSchema,
134
+ handler: async ({ input, ctx, meta }) => {
135
+ await ctx.db.insert(ctx.schema.auditLogs).values({
136
+ entityType: input.entityType,
137
+ entityId: input.entityId,
138
+ action: input.action,
139
+ message: input.message,
140
+ requestId: meta?.requestId,
141
+ actorId: meta?.actorId ?? null,
142
+ });
143
+
144
+ ctx.logger.info({
145
+ job: "create_audit_log",
146
+ requestId: meta?.requestId,
147
+ entityId: input.entityId,
148
+ });
149
+ },
150
+ }),
151
+
152
+ sendNotificationEmail: job("send_notification_email", {
153
+ input: notificationEmailSchema,
154
+ handler: async ({ input, ctx }) => {
155
+ await ctx.mailer.send({
156
+ to: input.email,
157
+ subject: input.subject,
158
+ text: input.message,
159
+ });
160
+ },
161
+ }),
162
+ };
163
+ },
169
164
  });
170
165
  ```
171
166
 
@@ -236,63 +231,45 @@ flowchart TD
236
231
 
237
232
  The canonical Flowli path is runtime-first:
238
233
 
239
- ```ts
240
- // src/flowli/jobs/create-audit-log.ts
241
- import * as v from "valibot";
242
- import { job } from "flowli";
243
- import type { AppContext } from "..";
244
-
245
- export const auditLogSchema = v.object({
246
- entityId: v.string(),
247
- action: v.string(),
248
- });
249
-
250
- export const auditLogMeta = v.object({
251
- requestId: v.string(),
252
- });
253
-
254
- export const createAuditLog = job.withContext<AppContext>()(
255
- "create_audit_log",
256
- {
257
- input: auditLogSchema,
258
- meta: auditLogMeta,
259
- handler: async ({ input, ctx, meta }) => {
260
- await ctx.db.insert("audit_logs").values({
261
- entityId: input.entityId,
262
- action: input.action,
263
- requestId: meta?.requestId,
264
- });
265
-
266
- ctx.logger.info({
267
- entityId: input.entityId,
268
- action: input.action,
269
- });
270
- },
271
- },
272
- );
273
- ```
274
-
275
- ```ts
276
- // src/flowli/jobs/index.ts
277
- export * from "./create-audit-log";
278
- ```
279
-
280
234
  ```ts
281
235
  // src/flowli/index.ts
236
+ import * as v from "valibot";
282
237
  import { defineJobs } from "flowli";
283
- import * as jobs from "./jobs";
284
-
285
- export type AppContext = {
286
- logger: typeof logger;
287
- db: typeof db;
288
- };
289
238
 
290
239
  export const flowli = defineJobs({
291
240
  context: {
292
241
  logger,
293
242
  db,
294
243
  },
295
- jobs,
244
+ jobs: ({ job }) => {
245
+ const auditLogSchema = v.object({
246
+ entityId: v.string(),
247
+ action: v.string(),
248
+ });
249
+
250
+ const auditLogMetaSchema = v.object({
251
+ requestId: v.string(),
252
+ });
253
+
254
+ return {
255
+ createAuditLog: job("create_audit_log", {
256
+ input: auditLogSchema,
257
+ meta: auditLogMetaSchema,
258
+ handler: async ({ input, ctx, meta }) => {
259
+ await ctx.db.insert("audit_logs").values({
260
+ entityId: input.entityId,
261
+ action: input.action,
262
+ requestId: meta?.requestId,
263
+ });
264
+
265
+ ctx.logger.info({
266
+ entityId: input.entityId,
267
+ action: input.action,
268
+ });
269
+ },
270
+ }),
271
+ };
272
+ },
296
273
  });
297
274
  ```
298
275
 
@@ -407,43 +384,9 @@ In short:
407
384
  To persist jobs, add a driver:
408
385
 
409
386
  ```ts
410
- // src/flowli/jobs/send-email.ts
411
387
  import * as v from "valibot";
412
- import { job } from "flowli";
413
- import type { AppContext } from "..";
414
-
415
- export const emailInputSchema = v.object({
416
- email: v.string(),
417
- subject: v.string(),
418
- });
419
-
420
- export const sendEmail = job.withContext<AppContext>()("send_email", {
421
- input: emailInputSchema,
422
- handler: async ({ input, ctx }) => {
423
- await ctx.mailer.send({
424
- to: input.email,
425
- subject: input.subject,
426
- });
427
- },
428
- });
429
- ```
430
-
431
- ```ts
432
- // src/flowli/jobs/index.ts
433
- export * from "./send-email";
434
- ```
435
-
436
- ```ts
437
- // src/flowli/index.ts
438
388
  import { defineJobs } from "flowli";
439
389
  import { ioredisDriver } from "flowli/ioredis";
440
- import * as jobs from "./jobs";
441
-
442
- export type AppContext = {
443
- db: typeof db;
444
- logger: typeof logger;
445
- mailer: typeof mailer;
446
- };
447
390
 
448
391
  export const flowli = defineJobs({
449
392
  driver: ioredisDriver({
@@ -455,7 +398,24 @@ export const flowli = defineJobs({
455
398
  logger,
456
399
  mailer,
457
400
  }),
458
- jobs,
401
+ jobs: ({ job }) => {
402
+ const emailInputSchema = v.object({
403
+ email: v.string(),
404
+ subject: v.string(),
405
+ });
406
+
407
+ return {
408
+ sendEmail: job("send_email", {
409
+ input: emailInputSchema,
410
+ handler: async ({ input, ctx }) => {
411
+ await ctx.mailer.send({
412
+ to: input.email,
413
+ subject: input.subject,
414
+ });
415
+ },
416
+ }),
417
+ };
418
+ },
459
419
  });
460
420
  ```
461
421
 
package/jsr.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://jsr.io/schema/config-file.v1.json",
3
3
  "name": "@alialnaghmoush/flowli",
4
- "version": "0.2.1",
4
+ "version": "0.2.2",
5
5
  "exports": {
6
6
  ".": "./src/index.ts",
7
7
  "./ioredis": "./src/ioredis.ts",
@@ -18,9 +18,19 @@
18
18
  "package.json"
19
19
  ],
20
20
  "exclude": [
21
+ ".cursor/",
22
+ ".git/",
23
+ ".gitignore",
24
+ ".npm-cache/",
25
+ "biome.json",
26
+ "bun.lock",
27
+ "docker-compose.yml",
21
28
  "dist/",
22
29
  "node_modules/",
23
30
  "test/",
31
+ "tsconfig.build.json",
32
+ "tsconfig.json",
33
+ "tsconfig.type-tests.json",
24
34
  "type-tests/"
25
35
  ]
26
36
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "flowli",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "Flowli is a jobs runtime with a code-first API, first-class execution strategies, runtime-scoped context injection, and pluggable Redis drivers.",
5
5
  "keywords": [
6
6
  "jobs",
@@ -20,6 +20,7 @@
20
20
  "type": "git",
21
21
  "url": "git+https://github.com/alialnaghmoush/flowli.git"
22
22
  },
23
+ "license": "MIT",
23
24
  "main": "./dist/index.js",
24
25
  "types": "./dist/index.d.ts",
25
26
  "type": "module",
@@ -68,9 +69,9 @@
68
69
  },
69
70
  "peerDependencies": {
70
71
  "@tanstack/react-start": "^1.0.0-rc || ^1",
71
- "ioredis": "",
72
+ "ioredis": "^5",
72
73
  "next": "^15 || ^16",
73
- "redis": "",
74
+ "redis": "^5",
74
75
  "typescript": "^5",
75
76
  "valibot": "^1.3.0",
76
77
  "zod": "^4.3.6"