@plurnk/plurnk-schedule 1.19.0
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/.env.defaults +15 -0
- package/README.md +45 -0
- package/SPEC.md +96 -0
- package/dist/Functionality.d.ts +102 -0
- package/dist/Functionality.d.ts.map +1 -0
- package/dist/Functionality.js +242 -0
- package/dist/Functionality.js.map +1 -0
- package/dist/Module.d.ts +21 -0
- package/dist/Module.d.ts.map +1 -0
- package/dist/Module.js +79 -0
- package/dist/Module.js.map +1 -0
- package/dist/ScheduleResources.d.ts +14 -0
- package/dist/ScheduleResources.d.ts.map +1 -0
- package/dist/ScheduleResources.js +22 -0
- package/dist/ScheduleResources.js.map +1 -0
- package/dist/Scheduler.d.ts +51 -0
- package/dist/Scheduler.d.ts.map +1 -0
- package/dist/Scheduler.js +205 -0
- package/dist/Scheduler.js.map +1 -0
- package/dist/config.d.ts +7 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +73 -0
- package/dist/config.js.map +1 -0
- package/dist/definition.d.ts +41 -0
- package/dist/definition.d.ts.map +1 -0
- package/dist/definition.js +52 -0
- package/dist/definition.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/rules.d.ts +19 -0
- package/dist/rules.d.ts.map +1 -0
- package/dist/rules.js +130 -0
- package/dist/rules.js.map +1 -0
- package/dist/temporal.d.ts +13 -0
- package/dist/temporal.d.ts.map +1 -0
- package/dist/temporal.js +9 -0
- package/dist/temporal.js.map +1 -0
- package/docs/schedule.md +62 -0
- package/package.json +62 -0
package/dist/rules.js
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
// {§schedule-rule} — a rule is RFC 5545 text: an optional DTSTART line and one RRULE line (bare
|
|
2
|
+
// `FREQ=…` parts are the RRULE line), with EXDATE/RDATE lines admitted. The recurrence library
|
|
3
|
+
// owns the grammar and the expansion; this module owns what the library is lenient about — unknown
|
|
4
|
+
// RRULE parts, extra lines, a DTSTART the effective zone must stamp ({§schedule-zone}) — and the
|
|
5
|
+
// bound a workspace rule must carry ({§schedule-bound}).
|
|
6
|
+
import { RRuleTemporal } from "rrule-temporal";
|
|
7
|
+
import { toText } from "rrule-temporal/totext";
|
|
8
|
+
import { zoned } from "./temporal.js";
|
|
9
|
+
const RRULE_PARTS = new Set([
|
|
10
|
+
"FREQ", "UNTIL", "COUNT", "INTERVAL", "BYSECOND", "BYMINUTE", "BYHOUR", "BYDAY",
|
|
11
|
+
"BYMONTHDAY", "BYYEARDAY", "BYWEEKNO", "BYMONTH", "BYSETPOS", "WKST",
|
|
12
|
+
]);
|
|
13
|
+
const DTSTART = /^DTSTART(?:;TZID=([^:]+))?:(\d{8}T\d{6})(Z?)$/u;
|
|
14
|
+
const RRULE = /^(?:RRULE:)?([A-Z]+=[^;=]+(?:;[A-Z]+=[^;=]+)*)$/u;
|
|
15
|
+
const DATE_LIST = /^(?:EXDATE|RDATE)(?:;[^:]+)?:.+$/u;
|
|
16
|
+
export class ScheduleRuleError extends Error {
|
|
17
|
+
code;
|
|
18
|
+
constructor(code, detail, cause) {
|
|
19
|
+
super(detail, cause === undefined ? undefined : { cause });
|
|
20
|
+
this.name = "ScheduleRuleError";
|
|
21
|
+
this.code = code;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const messageOf = (cause) => cause instanceof Error ? cause.message : String(cause);
|
|
25
|
+
const invalid = (detail, cause) => new ScheduleRuleError("rule-invalid", `The rule is not a readable RFC 5545 recurrence: ${detail}.`, cause);
|
|
26
|
+
// The rule's lines, checked by name: the library drops unknown RRULE parts and extra RRULE lines
|
|
27
|
+
// silently, and the DTSTART form decides how the zone applies.
|
|
28
|
+
const readLines = (text) => {
|
|
29
|
+
let dtstart = null;
|
|
30
|
+
let rrule = null;
|
|
31
|
+
const dateLists = [];
|
|
32
|
+
for (const line of text.split(/\r?\n/u).map((raw) => raw.trim()).filter((raw) => raw.length > 0)) {
|
|
33
|
+
const start = DTSTART.exec(line);
|
|
34
|
+
if (start !== null) {
|
|
35
|
+
if (dtstart !== null)
|
|
36
|
+
throw invalid("it carries more than one DTSTART");
|
|
37
|
+
dtstart = start;
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
if (DATE_LIST.test(line)) {
|
|
41
|
+
dateLists.push(line);
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const match = RRULE.exec(line);
|
|
45
|
+
if (match === null)
|
|
46
|
+
throw invalid(`the line '${line}' is neither DTSTART, RRULE, EXDATE nor RDATE`);
|
|
47
|
+
if (rrule !== null)
|
|
48
|
+
throw invalid("it carries more than one RRULE");
|
|
49
|
+
const unknown = match[1].split(";").map((part) => part.split("=")[0]).filter((name) => !RRULE_PARTS.has(name));
|
|
50
|
+
if (unknown.length > 0)
|
|
51
|
+
throw invalid(`RRULE has no part named ${unknown.join(", ")}`);
|
|
52
|
+
rrule = match[1];
|
|
53
|
+
}
|
|
54
|
+
if (rrule === null)
|
|
55
|
+
throw invalid("it has no RRULE line");
|
|
56
|
+
return { dtstart, rrule, dateLists };
|
|
57
|
+
};
|
|
58
|
+
export const assertZone = (zone) => {
|
|
59
|
+
try {
|
|
60
|
+
zoned(0, zone);
|
|
61
|
+
}
|
|
62
|
+
catch (cause) {
|
|
63
|
+
throw new ScheduleRuleError("zone-unknown", `'${zone}' is not a time zone the runtime knows; use an IANA name.`, cause);
|
|
64
|
+
}
|
|
65
|
+
};
|
|
66
|
+
const fromRule = (rule) => {
|
|
67
|
+
const options = rule.options();
|
|
68
|
+
if (options.tzid === undefined)
|
|
69
|
+
throw new Error("the recurrence library resolved a rule without a zone");
|
|
70
|
+
return {
|
|
71
|
+
text: rule.toString(),
|
|
72
|
+
zone: options.tzid,
|
|
73
|
+
bounded: options.count !== undefined || options.until !== undefined,
|
|
74
|
+
rule,
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
// Canonical text back into a rule; the text is the family's own, so a failure is a defect.
|
|
78
|
+
export const parseRule = (text) => {
|
|
79
|
+
try {
|
|
80
|
+
return fromRule(new RRuleTemporal({ rruleString: text, strict: true }));
|
|
81
|
+
}
|
|
82
|
+
catch (cause) {
|
|
83
|
+
throw invalid(messageOf(cause), cause);
|
|
84
|
+
}
|
|
85
|
+
};
|
|
86
|
+
// Authored text into a canonical rule. Without a DTSTART the rule starts at the next whole second
|
|
87
|
+
// in the effective zone, so its first occurrence is still ahead; a floating DTSTART is read in that
|
|
88
|
+
// zone; a zoned or UTC DTSTART keeps its own.
|
|
89
|
+
export const normalizeRule = (text, zone, nowMs) => {
|
|
90
|
+
assertZone(zone);
|
|
91
|
+
const { dtstart, rrule, dateLists } = readLines(text);
|
|
92
|
+
if (dtstart?.[1] !== undefined)
|
|
93
|
+
assertZone(dtstart[1]);
|
|
94
|
+
let rule;
|
|
95
|
+
try {
|
|
96
|
+
if (dtstart === null) {
|
|
97
|
+
const start = zoned(nowMs, zone).round({ smallestUnit: "second", roundingMode: "floor" }).add({ seconds: 1 });
|
|
98
|
+
rule = new RRuleTemporal({ rruleString: [`RRULE:${rrule}`, ...dateLists].join("\n"), dtstart: start, strict: true });
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
const floating = dtstart[1] === undefined && dtstart[3] === "";
|
|
102
|
+
rule = new RRuleTemporal({
|
|
103
|
+
rruleString: [dtstart[0], `RRULE:${rrule}`, ...dateLists].join("\n"),
|
|
104
|
+
...(floating ? { tzid: zone } : {}),
|
|
105
|
+
strict: true,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (cause) {
|
|
110
|
+
throw invalid(messageOf(cause), cause);
|
|
111
|
+
}
|
|
112
|
+
return parseRule(rule.toString());
|
|
113
|
+
};
|
|
114
|
+
// The first occurrence strictly after an instant; null once the rule is exhausted.
|
|
115
|
+
export const nextOccurrence = (parsed, afterMs) => parsed.rule.next(zoned(afterMs, parsed.zone)) ?? null;
|
|
116
|
+
export const upcoming = (parsed, afterMs, limit) => {
|
|
117
|
+
const occurrences = [];
|
|
118
|
+
let cursor = zoned(afterMs, parsed.zone);
|
|
119
|
+
while (occurrences.length < limit) {
|
|
120
|
+
const next = parsed.rule.next(cursor);
|
|
121
|
+
if (next === null || next === undefined)
|
|
122
|
+
break;
|
|
123
|
+
occurrences.push(next);
|
|
124
|
+
cursor = next;
|
|
125
|
+
}
|
|
126
|
+
return occurrences;
|
|
127
|
+
};
|
|
128
|
+
// The rule in words; the zone abbreviation is left out because the zone is named beside it.
|
|
129
|
+
export const describeRule = (parsed) => toText(parsed.rule, undefined, { excludeTzAbbreviation: true });
|
|
130
|
+
//# sourceMappingURL=rules.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rules.js","sourceRoot":"","sources":["../src/rules.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { TemporalImplementation, TemporalZonedDateTime } from "rrule-temporal";
|
|
2
|
+
export interface TemporalRuntime extends TemporalImplementation<TemporalZonedDateTime> {
|
|
3
|
+
readonly Instant: {
|
|
4
|
+
fromEpochMilliseconds(epochMilliseconds: number): {
|
|
5
|
+
toZonedDateTimeISO(timeZone: string): TemporalZonedDateTime;
|
|
6
|
+
};
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
declare const runtime: TemporalRuntime | undefined;
|
|
10
|
+
export default runtime;
|
|
11
|
+
export declare const zoned: (epochMilliseconds: number, zone: string) => TemporalZonedDateTime;
|
|
12
|
+
export declare const isoString: (moment: TemporalZonedDateTime) => string;
|
|
13
|
+
//# sourceMappingURL=temporal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"temporal.d.ts","sourceRoot":"","sources":["../src/temporal.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,sBAAsB,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAEpF,MAAM,WAAW,eAAgB,SAAQ,sBAAsB,CAAC,qBAAqB,CAAC;IAClF,QAAQ,CAAC,OAAO,EAAE;QACd,qBAAqB,CAAC,iBAAiB,EAAE,MAAM,GAAG;YAC9C,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,qBAAqB,CAAC;SAC/D,CAAC;KACL,CAAC;CACL;AAED,QAAA,MAAM,OAAO,6BAAqE,CAAC;eAGpE,OAAO;AAGtB,eAAO,MAAM,KAAK,sBAAuB,MAAM,QAAQ,MAAM,KAAG,qBACqB,CAAC;AAGtF,eAAO,MAAM,SAAS,WAAY,qBAAqB,KAAG,MAAqD,CAAC"}
|
package/dist/temporal.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
const runtime = globalThis.Temporal;
|
|
2
|
+
if (runtime === undefined)
|
|
3
|
+
throw new Error("@plurnk/plurnk-schedule requires a runtime with Temporal (Node 26 or later).");
|
|
4
|
+
export default runtime;
|
|
5
|
+
// One instant read in one IANA zone; an unknown zone is the runtime's RangeError.
|
|
6
|
+
export const zoned = (epochMilliseconds, zone) => runtime.Instant.fromEpochMilliseconds(epochMilliseconds).toZonedDateTimeISO(zone);
|
|
7
|
+
// The wire form of an occurrence: RFC 9557 with the zone name, to the second.
|
|
8
|
+
export const isoString = (moment) => moment.toString({ smallestUnit: "second" });
|
|
9
|
+
//# sourceMappingURL=temporal.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"temporal.js","sourceRoot":"","sources":["../src/temporal.ts"],"names":[],"mappings":"AAaA,MAAM,OAAO,GAAI,UAAwD,CAAC,QAAQ,CAAC;AACnF,IAAI,OAAO,KAAK,SAAS;IAAE,MAAM,IAAI,KAAK,CAAC,8EAA8E,CAAC,CAAC;AAE3H,eAAe,OAAO,CAAC;AAEvB,kFAAkF;AAClF,MAAM,CAAC,MAAM,KAAK,GAAG,CAAC,iBAAyB,EAAE,IAAY,EAAyB,EAAE,CACpF,OAAO,CAAC,OAAO,CAAC,qBAAqB,CAAC,iBAAiB,CAAC,CAAC,kBAAkB,CAAC,IAAI,CAAC,CAAC;AAEtF,8EAA8E;AAC9E,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,MAA6B,EAAU,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC,CAAC"}
|
package/docs/schedule.md
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
# schedule
|
|
2
|
+
|
|
3
|
+
A schedule delivers a message to a worker at each occurrence of a recurrence
|
|
4
|
+
rule. The rule is RFC 5545 text, the `RRULE` grammar calendars use, and the
|
|
5
|
+
message arrives like any other, from `schedule://<alias>`, joining the
|
|
6
|
+
worker's live loop or starting one. Your packet carries no clock: `discover`
|
|
7
|
+
tells the time when you ask.
|
|
8
|
+
|
|
9
|
+
## When to reach for a schedule
|
|
10
|
+
|
|
11
|
+
- Work that recurs on the calendar: a daily summary, an hourly check, one
|
|
12
|
+
reminder at a set time.
|
|
13
|
+
- Something external to wait for that sends no signal: schedule the check
|
|
14
|
+
instead of polling from inside a loop.
|
|
15
|
+
- Not for a child worker or a task you started: those wake you when they
|
|
16
|
+
conclude.
|
|
17
|
+
|
|
18
|
+
## discover: read the time, preview a rule
|
|
19
|
+
|
|
20
|
+
`discover` takes `{"source": "<rule text>"}` and returns one inert candidate.
|
|
21
|
+
Its summary opens with the current time in the effective zone and previews
|
|
22
|
+
the first occurrences; its definition carries the rule as it would be stored.
|
|
23
|
+
Nothing is persisted.
|
|
24
|
+
|
|
25
|
+
````schedule (discover) <!-- what time is it, and when would this fire -->
|
|
26
|
+
{"source": "FREQ=DAILY;BYHOUR=9;BYMINUTE=0;BYSECOND=0;COUNT=20"}
|
|
27
|
+
````
|
|
28
|
+
|
|
29
|
+
## add
|
|
30
|
+
|
|
31
|
+
`add` persists the rule for this workspace and arms it. It is a host
|
|
32
|
+
effect: it proposes and runs only on acceptance.
|
|
33
|
+
|
|
34
|
+
````schedule (add)
|
|
35
|
+
{"alias": "standup", "definition": {"rule": "FREQ=DAILY;BYHOUR=9;BYMINUTE=0;BYSECOND=0;COUNT=20", "target": "worker://scribe", "prompt": "Summarize yesterday's log entries for the team."}}
|
|
36
|
+
````
|
|
37
|
+
|
|
38
|
+
- `rule`: bare `FREQ=…` parts, or a `DTSTART` line and an `RRULE` line. A
|
|
39
|
+
workspace rule ends: give it `COUNT` or `UNTIL`. Without a `DTSTART` the
|
|
40
|
+
rule starts at the next whole second when added; `BYHOUR`, `BYMINUTE`,
|
|
41
|
+
`BYSECOND` and `BYDAY` place it.
|
|
42
|
+
- `target`: the worker that receives the message, `worker://<name>`; any
|
|
43
|
+
worker in this workspace, yourself included.
|
|
44
|
+
- `prompt`: the message delivered at each occurrence.
|
|
45
|
+
- `policy`: `{"proposals": "accept"}` when the delivery starts a loop no one
|
|
46
|
+
is watching and it must act on its own proposals; absent, the worker's
|
|
47
|
+
default holds.
|
|
48
|
+
|
|
49
|
+
Times are read in `TZ`: the workspace's `env` family sets it for every rule
|
|
50
|
+
and every command; UTC otherwise.
|
|
51
|
+
|
|
52
|
+
## list, enable, disable, remove
|
|
53
|
+
|
|
54
|
+
`list` shows each rule with its zone, its wording, and its next occurrence,
|
|
55
|
+
or `exhausted` once it has run out. A rule that could not deliver, because its
|
|
56
|
+
worker is gone, is `unavailable` with the exact Problem; `enable` retries it.
|
|
57
|
+
`disable` disarms without forgetting; `remove` forgets.
|
|
58
|
+
|
|
59
|
+
## Receiving a scheduled message
|
|
60
|
+
|
|
61
|
+
It arrives as an open message from `schedule://<alias>`, in the loop you are
|
|
62
|
+
in or in a new one. Answer it as you would any message.
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@plurnk/plurnk-schedule",
|
|
3
|
+
"version": "1.19.0",
|
|
4
|
+
"description": "Scheduled messages for Plurnk workers, on RFC 5545 recurrence rules",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/plurnk/plurnk-service.git",
|
|
10
|
+
"directory": "plurnk-schedule"
|
|
11
|
+
},
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"plurnk-dev": "./src/index.ts",
|
|
15
|
+
"types": "./dist/index.d.ts",
|
|
16
|
+
"default": "./dist/index.js"
|
|
17
|
+
},
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
".env.defaults",
|
|
22
|
+
"dist/**/*",
|
|
23
|
+
"docs/**/*",
|
|
24
|
+
"README.md",
|
|
25
|
+
"SPEC.md"
|
|
26
|
+
],
|
|
27
|
+
"engines": {
|
|
28
|
+
"node": ">=26"
|
|
29
|
+
},
|
|
30
|
+
"scripts": {
|
|
31
|
+
"test": "npm run test:lint && npm run test:unit && npm run test:intg",
|
|
32
|
+
"test:lint": "tsc --noEmit",
|
|
33
|
+
"test:unit": "node --conditions=plurnk-dev --env-file=.env.defaults --test src/*.test.ts",
|
|
34
|
+
"test:intg": "PLURNK_TEST_RUN=$(date -u +%Y%m%dT%H%M%SZ) node --conditions=plurnk-dev --env-file=.env.defaults --test test/intg/*.test.ts",
|
|
35
|
+
"build:clean": "rm -rf dist",
|
|
36
|
+
"build:dist": "tsc -p tsconfig.build.json",
|
|
37
|
+
"build": "npm run build:clean && npm run build:dist",
|
|
38
|
+
"prepack": "npm run build",
|
|
39
|
+
"prepublishOnly": "npm audit --audit-level=moderate && npm test"
|
|
40
|
+
},
|
|
41
|
+
"dependencies": {
|
|
42
|
+
"rrule-temporal": "2.2.6"
|
|
43
|
+
},
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"@plurnk/plurnk-contracts": "^1.19.0",
|
|
46
|
+
"@plurnk/plurnk-schemes": "^1.19.0"
|
|
47
|
+
},
|
|
48
|
+
"keywords": [
|
|
49
|
+
"plurnk",
|
|
50
|
+
"schedule",
|
|
51
|
+
"rrule",
|
|
52
|
+
"recurrence"
|
|
53
|
+
],
|
|
54
|
+
"homepage": "https://github.com/plurnk/plurnk-service/tree/main/plurnk-schedule#readme",
|
|
55
|
+
"bugs": {
|
|
56
|
+
"url": "https://repo.possumtech.com/plurnk/plurnk-service/issues"
|
|
57
|
+
},
|
|
58
|
+
"author": "@wikitopian",
|
|
59
|
+
"publishConfig": {
|
|
60
|
+
"access": "public"
|
|
61
|
+
}
|
|
62
|
+
}
|