flowli 0.2.1
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 +21 -0
- package/README.md +815 -0
- package/dist/bun-redis.d.ts +1 -0
- package/dist/bun-redis.js +3 -0
- package/dist/core/define-jobs.d.ts +8 -0
- package/dist/core/define-jobs.js +47 -0
- package/dist/core/errors.d.ts +21 -0
- package/dist/core/errors.js +35 -0
- package/dist/core/job.d.ts +9 -0
- package/dist/core/job.js +20 -0
- package/dist/core/types.d.ts +175 -0
- package/dist/core/types.js +1 -0
- package/dist/driver/duration.d.ts +2 -0
- package/dist/driver/duration.js +23 -0
- package/dist/driver/encoding.d.ts +2 -0
- package/dist/driver/encoding.js +9 -0
- package/dist/driver/keys.d.ts +15 -0
- package/dist/driver/keys.js +14 -0
- package/dist/driver/records.d.ts +25 -0
- package/dist/driver/records.js +61 -0
- package/dist/driver/scheduling.d.ts +6 -0
- package/dist/driver/scheduling.js +127 -0
- package/dist/drivers/bun-redis.d.ts +24 -0
- package/dist/drivers/bun-redis.js +21 -0
- package/dist/drivers/ioredis.d.ts +16 -0
- package/dist/drivers/ioredis.js +31 -0
- package/dist/drivers/redis.d.ts +27 -0
- package/dist/drivers/redis.js +23 -0
- package/dist/drivers/shared.d.ts +21 -0
- package/dist/drivers/shared.js +172 -0
- package/dist/hono.d.ts +1 -0
- package/dist/hono.js +3 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +11 -0
- package/dist/integrations/hono.d.ts +10 -0
- package/dist/integrations/hono.js +7 -0
- package/dist/integrations/next.d.ts +18 -0
- package/dist/integrations/tanstack-start.d.ts +20 -0
- package/dist/ioredis.d.ts +1 -0
- package/dist/ioredis.js +3 -0
- package/dist/next.d.ts +1 -0
- package/dist/next.js +4 -0
- package/dist/redis.d.ts +1 -0
- package/dist/redis.js +3 -0
- package/dist/runner/create-runner.d.ts +3 -0
- package/dist/runner/create-runner.js +104 -0
- package/dist/runner/types.d.ts +20 -0
- package/dist/runner/types.js +1 -0
- package/dist/runner.d.ts +2 -0
- package/dist/runner.js +3 -0
- package/dist/runtime/create-job-surface.d.ts +2 -0
- package/dist/runtime/create-job-surface.js +24 -0
- package/dist/runtime/invoke-handler.d.ts +2 -0
- package/dist/runtime/invoke-handler.js +7 -0
- package/dist/runtime/normalize-jobs.d.ts +5 -0
- package/dist/runtime/normalize-jobs.js +14 -0
- package/dist/runtime/resolve-context.d.ts +2 -0
- package/dist/runtime/resolve-context.js +6 -0
- package/dist/runtime/validate.d.ts +2 -0
- package/dist/runtime/validate.js +15 -0
- package/dist/strategies/delay.d.ts +2 -0
- package/dist/strategies/delay.js +32 -0
- package/dist/strategies/enqueue.d.ts +2 -0
- package/dist/strategies/enqueue.js +30 -0
- package/dist/strategies/run.d.ts +2 -0
- package/dist/strategies/run.js +10 -0
- package/dist/strategies/schedule.d.ts +2 -0
- package/dist/strategies/schedule.js +33 -0
- package/dist/tanstack-start.d.ts +1 -0
- package/dist/tanstack-start.js +4 -0
- package/jsr.json +26 -0
- package/package.json +135 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { FlowliValidationError } from "../core/errors.js";
|
|
2
|
+
export async function validateWithSchema(schema, value, label) {
|
|
3
|
+
const result = (await schema["~standard"].validate(value));
|
|
4
|
+
if (isFailure(result)) {
|
|
5
|
+
const issueSummary = result.issues.map((issue) => issue.message).join(", ");
|
|
6
|
+
throw new FlowliValidationError(`${label} validation failed: ${issueSummary || "unknown validation issue"}`, result.issues);
|
|
7
|
+
}
|
|
8
|
+
return result.value;
|
|
9
|
+
}
|
|
10
|
+
function isFailure(result) {
|
|
11
|
+
return Boolean(result &&
|
|
12
|
+
typeof result === "object" &&
|
|
13
|
+
"issues" in result &&
|
|
14
|
+
Array.isArray(result.issues));
|
|
15
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import type { AnyJobDefinition, DelayValue, FlowliContextRecord, FlowliRuntimeInternals, JobDefaults, JobReceipt, PersistedInvocationOptions } from "../core/types.js";
|
|
2
|
+
export declare function delayStrategy<TJob extends AnyJobDefinition, TContext extends FlowliContextRecord>(job: TJob, internals: FlowliRuntimeInternals<Record<string, TJob>, TContext>, defaults: JobDefaults, now: number, delay: DelayValue, input: unknown, options?: PersistedInvocationOptions<unknown>): Promise<JobReceipt>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { FlowliStrategyError } from "../core/errors.js";
|
|
2
|
+
import { parseDelay } from "../driver/duration.js";
|
|
3
|
+
import { createPersistedJobRecord } from "../driver/records.js";
|
|
4
|
+
import { createJobId } from "../driver/scheduling.js";
|
|
5
|
+
import { validateWithSchema } from "../runtime/validate.js";
|
|
6
|
+
export async function delayStrategy(job, internals, defaults, now, delay, input, options) {
|
|
7
|
+
const driver = requireDriver(internals.driver, job.name, "delay");
|
|
8
|
+
const validatedInput = await validateWithSchema(job.input, input, `${job.name} input`);
|
|
9
|
+
const validatedMeta = job.meta
|
|
10
|
+
? await validateWithSchema(job.meta, options?.meta, `${job.name} meta`)
|
|
11
|
+
: undefined;
|
|
12
|
+
const scheduledFor = now + parseDelay(delay);
|
|
13
|
+
const record = createPersistedJobRecord({
|
|
14
|
+
id: createJobId(),
|
|
15
|
+
name: job.name,
|
|
16
|
+
input: validatedInput,
|
|
17
|
+
meta: validatedMeta,
|
|
18
|
+
scheduledFor,
|
|
19
|
+
maxAttempts: options?.maxAttempts ?? defaults.maxAttempts ?? 1,
|
|
20
|
+
...((options?.backoff ?? defaults.backoff)
|
|
21
|
+
? { backoff: options?.backoff ?? defaults.backoff }
|
|
22
|
+
: {}),
|
|
23
|
+
now,
|
|
24
|
+
});
|
|
25
|
+
return driver.enqueue(record);
|
|
26
|
+
}
|
|
27
|
+
function requireDriver(driver, jobName, strategy) {
|
|
28
|
+
if (!driver) {
|
|
29
|
+
throw new FlowliStrategyError(`Job "${jobName}" cannot use ${strategy}() without a configured driver.`);
|
|
30
|
+
}
|
|
31
|
+
return driver;
|
|
32
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import type { AnyJobDefinition, FlowliContextRecord, FlowliRuntimeInternals, JobDefaults, JobReceipt, PersistedInvocationOptions } from "../core/types.js";
|
|
2
|
+
export declare function enqueueStrategy<TJob extends AnyJobDefinition, TContext extends FlowliContextRecord>(job: TJob, internals: FlowliRuntimeInternals<Record<string, TJob>, TContext>, defaults: JobDefaults, now: number, input: unknown, options?: PersistedInvocationOptions<unknown>): Promise<JobReceipt>;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { FlowliStrategyError } from "../core/errors.js";
|
|
2
|
+
import { createPersistedJobRecord } from "../driver/records.js";
|
|
3
|
+
import { createJobId } from "../driver/scheduling.js";
|
|
4
|
+
import { validateWithSchema } from "../runtime/validate.js";
|
|
5
|
+
export async function enqueueStrategy(job, internals, defaults, now, input, options) {
|
|
6
|
+
const driver = requireDriver(internals.driver, job.name, "enqueue");
|
|
7
|
+
const validatedInput = await validateWithSchema(job.input, input, `${job.name} input`);
|
|
8
|
+
const validatedMeta = job.meta
|
|
9
|
+
? await validateWithSchema(job.meta, options?.meta, `${job.name} meta`)
|
|
10
|
+
: undefined;
|
|
11
|
+
const record = createPersistedJobRecord({
|
|
12
|
+
id: createJobId(),
|
|
13
|
+
name: job.name,
|
|
14
|
+
input: validatedInput,
|
|
15
|
+
meta: validatedMeta,
|
|
16
|
+
scheduledFor: now,
|
|
17
|
+
maxAttempts: options?.maxAttempts ?? defaults.maxAttempts ?? 1,
|
|
18
|
+
...((options?.backoff ?? defaults.backoff)
|
|
19
|
+
? { backoff: options?.backoff ?? defaults.backoff }
|
|
20
|
+
: {}),
|
|
21
|
+
now,
|
|
22
|
+
});
|
|
23
|
+
return driver.enqueue(record);
|
|
24
|
+
}
|
|
25
|
+
function requireDriver(driver, jobName, strategy) {
|
|
26
|
+
if (!driver) {
|
|
27
|
+
throw new FlowliStrategyError(`Job "${jobName}" cannot use ${strategy}() without a configured driver.`);
|
|
28
|
+
}
|
|
29
|
+
return driver;
|
|
30
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import type { AnyJobDefinition, FlowliContextRecord, FlowliRuntimeInternals, JobDefaults, JobMeta, JobResult } from "../core/types.js";
|
|
2
|
+
export declare function runStrategy<TJob extends AnyJobDefinition, TContext extends FlowliContextRecord>(job: TJob, internals: FlowliRuntimeInternals<Record<string, TJob>, TContext>, _defaults: JobDefaults, input: unknown, meta: JobMeta<TJob> | undefined): Promise<JobResult<TJob>>;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { invokeHandler } from "../runtime/invoke-handler.js";
|
|
2
|
+
import { validateWithSchema } from "../runtime/validate.js";
|
|
3
|
+
export async function runStrategy(job, internals, _defaults, input, meta) {
|
|
4
|
+
const validatedInput = await validateWithSchema(job.input, input, `${job.name} input`);
|
|
5
|
+
const validatedMeta = job.meta
|
|
6
|
+
? await validateWithSchema(job.meta, meta, `${job.name} meta`)
|
|
7
|
+
: undefined;
|
|
8
|
+
const context = await internals.context();
|
|
9
|
+
return invokeHandler(job, validatedInput, context, validatedMeta);
|
|
10
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import type { AnyJobDefinition, FlowliContextRecord, FlowliRuntimeInternals, JobDefaults, ScheduleInvocation, ScheduleReceipt } from "../core/types.js";
|
|
2
|
+
export declare function scheduleStrategy<TJob extends AnyJobDefinition, TContext extends FlowliContextRecord>(job: TJob, internals: FlowliRuntimeInternals<Record<string, TJob>, TContext>, defaults: JobDefaults, now: number, invocation: ScheduleInvocation<unknown, unknown>): Promise<ScheduleReceipt>;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { FlowliStrategyError } from "../core/errors.js";
|
|
2
|
+
import { createScheduleRecord } from "../driver/records.js";
|
|
3
|
+
import { deriveScheduleKey, getNextCronRun, validateCron, } from "../driver/scheduling.js";
|
|
4
|
+
import { validateWithSchema } from "../runtime/validate.js";
|
|
5
|
+
export async function scheduleStrategy(job, internals, defaults, now, invocation) {
|
|
6
|
+
const driver = requireDriver(internals.driver, job.name, "schedule");
|
|
7
|
+
validateCron(invocation.cron);
|
|
8
|
+
const validatedInput = await validateWithSchema(job.input, invocation.input, `${job.name} input`);
|
|
9
|
+
const validatedMeta = job.meta
|
|
10
|
+
? await validateWithSchema(job.meta, invocation.meta, `${job.name} meta`)
|
|
11
|
+
: undefined;
|
|
12
|
+
const key = invocation.key ??
|
|
13
|
+
deriveScheduleKey(job.name, invocation.cron, validatedInput);
|
|
14
|
+
const nextRunAt = getNextCronRun(invocation.cron, now);
|
|
15
|
+
const record = createScheduleRecord({
|
|
16
|
+
key,
|
|
17
|
+
name: job.name,
|
|
18
|
+
cron: invocation.cron,
|
|
19
|
+
input: validatedInput,
|
|
20
|
+
meta: validatedMeta,
|
|
21
|
+
maxAttempts: defaults.maxAttempts ?? 1,
|
|
22
|
+
...(defaults.backoff ? { backoff: defaults.backoff } : {}),
|
|
23
|
+
nextRunAt,
|
|
24
|
+
now,
|
|
25
|
+
});
|
|
26
|
+
return driver.registerSchedule(record);
|
|
27
|
+
}
|
|
28
|
+
function requireDriver(driver, jobName, strategy) {
|
|
29
|
+
if (!driver) {
|
|
30
|
+
throw new FlowliStrategyError(`Job "${jobName}" cannot use ${strategy}() without a configured driver.`);
|
|
31
|
+
}
|
|
32
|
+
return driver;
|
|
33
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { type TanStackStartRouteContext, type TanStackStartRouteHandler, type TanStackStartRouteHandlerArgs, type TanStackStartRouteParams, type TanStackStartServerFnHandler, type TanStackStartServerFnTools, tanstackStartRoute, tanstackStartServerFn, } from "./integrations/tanstack-start.js";
|
package/jsr.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://jsr.io/schema/config-file.v1.json",
|
|
3
|
+
"name": "@alialnaghmoush/flowli",
|
|
4
|
+
"version": "0.2.1",
|
|
5
|
+
"exports": {
|
|
6
|
+
".": "./src/index.ts",
|
|
7
|
+
"./ioredis": "./src/ioredis.ts",
|
|
8
|
+
"./redis": "./src/redis.ts",
|
|
9
|
+
"./bun-redis": "./src/bun-redis.ts",
|
|
10
|
+
"./next": "./src/next.ts",
|
|
11
|
+
"./tanstack-start": "./src/tanstack-start.ts",
|
|
12
|
+
"./hono": "./src/hono.ts",
|
|
13
|
+
"./runner": "./src/runner.ts"
|
|
14
|
+
},
|
|
15
|
+
"include": [
|
|
16
|
+
"README.md",
|
|
17
|
+
"src/**/*.ts",
|
|
18
|
+
"package.json"
|
|
19
|
+
],
|
|
20
|
+
"exclude": [
|
|
21
|
+
"dist/",
|
|
22
|
+
"node_modules/",
|
|
23
|
+
"test/",
|
|
24
|
+
"type-tests/"
|
|
25
|
+
]
|
|
26
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "flowli",
|
|
3
|
+
"version": "0.2.1",
|
|
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
|
+
"keywords": [
|
|
6
|
+
"jobs",
|
|
7
|
+
"queue",
|
|
8
|
+
"redis",
|
|
9
|
+
"nextjs",
|
|
10
|
+
"tanstack-start",
|
|
11
|
+
"typescript",
|
|
12
|
+
"background-jobs",
|
|
13
|
+
"scheduler"
|
|
14
|
+
],
|
|
15
|
+
"homepage": "https://github.com/alialnaghmoush/flowli#readme",
|
|
16
|
+
"bugs": {
|
|
17
|
+
"url": "https://github.com/alialnaghmoush/flowli/issues"
|
|
18
|
+
},
|
|
19
|
+
"repository": {
|
|
20
|
+
"type": "git",
|
|
21
|
+
"url": "git+https://github.com/alialnaghmoush/flowli.git"
|
|
22
|
+
},
|
|
23
|
+
"main": "./dist/index.js",
|
|
24
|
+
"types": "./dist/index.d.ts",
|
|
25
|
+
"type": "module",
|
|
26
|
+
"sideEffects": false,
|
|
27
|
+
"packageManager": "bun@1.3.10",
|
|
28
|
+
"engines": {
|
|
29
|
+
"node": ">=18"
|
|
30
|
+
},
|
|
31
|
+
"files": [
|
|
32
|
+
"dist",
|
|
33
|
+
"README.md",
|
|
34
|
+
"jsr.json"
|
|
35
|
+
],
|
|
36
|
+
"scripts": {
|
|
37
|
+
"build:js": "bun build ./src/index.ts ./src/ioredis.ts ./src/redis.ts ./src/bun-redis.ts ./src/next.ts ./src/tanstack-start.ts ./src/hono.ts ./src/runner.ts --outdir dist --root ./src --target node --format esm",
|
|
38
|
+
"build:types": "tsc -p tsconfig.build.json",
|
|
39
|
+
"build": "bun run build:js && bun run build:types",
|
|
40
|
+
"format": "biome format --write .",
|
|
41
|
+
"lint": "biome check .",
|
|
42
|
+
"check": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.type-tests.json --noEmit",
|
|
43
|
+
"prepack": "bun run verify",
|
|
44
|
+
"pack:npm": "npm pack --dry-run --cache ./.npm-cache",
|
|
45
|
+
"publish:npm-d": "npm publish --dry-run --cache ./.npm-cache",
|
|
46
|
+
"publish:npm": "npm publish --cache ./.npm-cache",
|
|
47
|
+
"publish:jsr-d": "npx jsr publish --dry-run",
|
|
48
|
+
"publish:jsr": "npx jsr publish",
|
|
49
|
+
"publish:all-d": "bun run publish:npm-d && bun run publish:jsr-d",
|
|
50
|
+
"publish:all": "bun run publish:npm && bun run publish:jsr",
|
|
51
|
+
"docker:up": "docker compose up -d",
|
|
52
|
+
"docker:down": "docker compose down -v",
|
|
53
|
+
"test:redis:docker": "FLOWLI_REDIS_URL=redis://127.0.0.1:6379/0 bun run test:redis",
|
|
54
|
+
"test": "bun test",
|
|
55
|
+
"test:redis": "bun test test/redis.integration.test.ts",
|
|
56
|
+
"verify:publish": "bun run pack:npm",
|
|
57
|
+
"verify": "bun run lint && bun run check && bun run build && bun test"
|
|
58
|
+
},
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"registry": "https://registry.npmjs.org/"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"@biomejs/biome": "^2.4.7",
|
|
64
|
+
"@types/bun": "latest",
|
|
65
|
+
"ioredis": "^5.10.0",
|
|
66
|
+
"redis": "^5.11.0",
|
|
67
|
+
"typescript": "^5.9.3"
|
|
68
|
+
},
|
|
69
|
+
"peerDependencies": {
|
|
70
|
+
"@tanstack/react-start": "^1.0.0-rc || ^1",
|
|
71
|
+
"ioredis": "",
|
|
72
|
+
"next": "^15 || ^16",
|
|
73
|
+
"redis": "",
|
|
74
|
+
"typescript": "^5",
|
|
75
|
+
"valibot": "^1.3.0",
|
|
76
|
+
"zod": "^4.3.6"
|
|
77
|
+
},
|
|
78
|
+
"peerDependenciesMeta": {
|
|
79
|
+
"@tanstack/react-start": {
|
|
80
|
+
"optional": true
|
|
81
|
+
},
|
|
82
|
+
"ioredis": {
|
|
83
|
+
"optional": true
|
|
84
|
+
},
|
|
85
|
+
"next": {
|
|
86
|
+
"optional": true
|
|
87
|
+
},
|
|
88
|
+
"redis": {
|
|
89
|
+
"optional": true
|
|
90
|
+
},
|
|
91
|
+
"typescript": {
|
|
92
|
+
"optional": true
|
|
93
|
+
},
|
|
94
|
+
"valibot": {
|
|
95
|
+
"optional": true
|
|
96
|
+
},
|
|
97
|
+
"zod": {
|
|
98
|
+
"optional": true
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
"exports": {
|
|
102
|
+
".": {
|
|
103
|
+
"types": "./dist/index.d.ts",
|
|
104
|
+
"import": "./dist/index.js"
|
|
105
|
+
},
|
|
106
|
+
"./ioredis": {
|
|
107
|
+
"types": "./dist/ioredis.d.ts",
|
|
108
|
+
"import": "./dist/ioredis.js"
|
|
109
|
+
},
|
|
110
|
+
"./redis": {
|
|
111
|
+
"types": "./dist/redis.d.ts",
|
|
112
|
+
"import": "./dist/redis.js"
|
|
113
|
+
},
|
|
114
|
+
"./bun-redis": {
|
|
115
|
+
"types": "./dist/bun-redis.d.ts",
|
|
116
|
+
"import": "./dist/bun-redis.js"
|
|
117
|
+
},
|
|
118
|
+
"./next": {
|
|
119
|
+
"types": "./dist/next.d.ts",
|
|
120
|
+
"import": "./dist/next.js"
|
|
121
|
+
},
|
|
122
|
+
"./tanstack-start": {
|
|
123
|
+
"types": "./dist/tanstack-start.d.ts",
|
|
124
|
+
"import": "./dist/tanstack-start.js"
|
|
125
|
+
},
|
|
126
|
+
"./hono": {
|
|
127
|
+
"types": "./dist/hono.d.ts",
|
|
128
|
+
"import": "./dist/hono.js"
|
|
129
|
+
},
|
|
130
|
+
"./runner": {
|
|
131
|
+
"types": "./dist/runner.d.ts",
|
|
132
|
+
"import": "./dist/runner.js"
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|