@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 ADDED
@@ -0,0 +1,15 @@
1
+ # @plurnk/plurnk-schedule — scheduled messages for workers.
2
+
3
+ # Time zone for schedules and for every command a worker runs; an IANA name. UTC unless the
4
+ # operator or a worker (through env) says otherwise; a rule without a TZID is stamped with the
5
+ # effective zone when it is added.
6
+ TZ=UTC
7
+ # Initially enabled service-origin rule aliases, JSON array; [] = none.
8
+ PLURNK_SCHEDULE_ENABLED=[]
9
+ # Upcoming occurrences shown when rule text is read before it is added, positive integer.
10
+ PLURNK_SCHEDULE_PREVIEW_OCCURRENCES=3
11
+ # A service-origin rule: the operator's crontab. JSON with the RFC 5545 rule text (without a
12
+ # DTSTART it starts when the daemon starts), the target worker, the message, and an optional
13
+ # loop policy. Disable-only for a worker; may be unbounded.
14
+ # An empty alias definition masks the service rule and its ENABLED selection.
15
+ # PLURNK_SCHEDULE_heartbeat={"rule":"FREQ=HOURLY","target":"worker://plurnkbot","prompt":"Check for new messages and report."}
package/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # @plurnk/plurnk-schedule
2
+
3
+ Scheduled messages for [Plurnk](https://github.com/plurnk/plurnk-service)
4
+ workers, on RFC 5545 recurrence rules. A rule delivers its message to a
5
+ worker at each occurrence, as an ordinary message from `schedule://<alias>`;
6
+ the worker's live loop takes it, or a new loop starts. Workers manage their
7
+ own rules through the `schedule` family (`discover`, `add`, `list`, `enable`,
8
+ `disable`, `remove`); the operator can seed rules from the environment.
9
+
10
+ ## Configure
11
+
12
+ Put overrides in the shell, `./.env`, or `$XDG_CONFIG_HOME/plurnk/.env`:
13
+
14
+ ```dotenv
15
+ TZ=America/New_York
16
+ PLURNK_SCHEDULE_HEARTBEAT={"rule":"FREQ=HOURLY","target":"worker://plurnkbot","prompt":"Check for new messages and report."}
17
+ PLURNK_SCHEDULE_ENABLED=["heartbeat"]
18
+ ```
19
+
20
+ - `TZ` is the zone rules are read in and the time is told in. The package
21
+ defaults it to UTC; the shell overrides; a workspace overrides through the
22
+ `env` family.
23
+ - `PLURNK_SCHEDULE_<ALIAS>` is one definition as JSON: `rule`, `target`
24
+ (`worker://<name>`), `prompt`, and optionally `policy`
25
+ (`{"proposals":"accept"}` for a loop nobody watches). A service rule may be
26
+ unbounded; a rule a worker adds carries `COUNT` or `UNTIL`. Without a
27
+ `DTSTART` a service rule starts when the daemon starts: `FREQ=HOURLY` is a
28
+ check-in at start and every hour after; `BYHOUR`, `BYMINUTE` and `BYSECOND`
29
+ pin a time of day instead.
30
+ - `PLURNK_SCHEDULE_ENABLED` names the aliases a workspace starts with.
31
+
32
+ ## Behaviour
33
+
34
+ - No packet carries a clock; a worker reads the time through `discover`.
35
+ - One armed occurrence per rule. A late fire delivers once and skips what it
36
+ missed.
37
+ - Timers outlive a workspace's residency and re-arm at daemon start from the
38
+ persisted family state.
39
+ - A delivery that fails, because the target worker is gone, lists the rule
40
+ `unavailable` with its Problem; `enable` retries.
41
+ - An occurrence arrives as a message and runs its own loop on the target
42
+ worker; no WAIT holds a loop for it.
43
+
44
+ `SPEC.md` is the specification; `docs/schedule.md` is the model-facing
45
+ teaching beneath the generated family document.
package/SPEC.md ADDED
@@ -0,0 +1,96 @@
1
+ # Plurnk schedule specification
2
+
3
+ ## §schedule-family The schedule family
4
+
5
+ Scheduled messages are one workspace Functionality family named `schedule`,
6
+ owned by `@plurnk/plurnk-schedule` ({§functionality-adapter}). A definition is
7
+ `{ rule, target, prompt, policy? }`: RFC 5545 rule text ({§schedule-rule}), a
8
+ `worker://<name>` target in the workspace ({§worker-name}, preserved exactly), the message delivered at each
9
+ occurrence, and optionally the proposal policy of a loop the delivery starts
10
+ ({§schedule-delivery}). Aliases take the shared grammar. The verbs are the
11
+ coordinator's: `list`, `discover` ({§schedule-clock}), `add`, `enable`,
12
+ `disable`, `remove`. The adapter publishes no documents of its own; the
13
+ family document's authored body is `docs/schedule.md`
14
+ ({§functionality-model-projection}).
15
+
16
+ ## §schedule-rule Rule text
17
+
18
+ A rule is RFC 5545 text: an optional `DTSTART` line, exactly one `RRULE` line,
19
+ and any `EXDATE` or `RDATE` lines. Bare `FREQ=…` parts are the RRULE line; the
20
+ `RRULE:` prefix is optional. The recurrence library owns the grammar and the
21
+ expansion. The family refuses by name what the library would drop silently: an
22
+ RRULE part outside the fourteen RFC 5545 names, a second RRULE, a second
23
+ DTSTART, any other line. Admission stores the library's canonical text,
24
+ `DTSTART;TZID=<zone>:<local>` then `RRULE:<parts>` then the date lines, and
25
+ every later read parses that text; a canonical text that fails to parse is a
26
+ defect, not a refusal.
27
+
28
+ ## §schedule-bound Bound
29
+
30
+ A workspace rule ends: its RRULE carries `COUNT` or `UNTIL`, else `add`
31
+ refuses with `rule-unbounded`. A service rule ({§schedule-environment}) may be
32
+ unbounded; the operator owns it. An exhausted rule stays listed `active` with
33
+ `exhausted: true` and `next: null`; it arms nothing.
34
+
35
+ ## §schedule-zone Zone
36
+
37
+ `TZ` is the zone a rule is read in and the zone the time is told in. The
38
+ package declares `TZ=UTC` in `.env.defaults`; the operator's environment
39
+ overrides it, a workspace overrides it through the `env` family
40
+ ({§workspace-env}), and a Worker's own `env` override wins for the verbs that
41
+ Worker invokes ({§functionality-scope}). A call carrying `env` metadata with
42
+ `TZ` is read in that zone for that call. A rule keeps the zone stamped at its
43
+ `add`; a later `env` change moves no existing rule. Without a `DTSTART` a rule starts when it is read, at the
44
+ next whole second in the effective zone, so its first occurrence is still
45
+ ahead: a workspace rule is read at `add`, a service rule once, when the daemon
46
+ starts, in the service's zone. A floating `DTSTART` is read in the effective
47
+ zone; a `DTSTART` with a `TZID` or a UTC `Z` keeps its own zone. An unknown
48
+ zone refuses with `zone-unknown`.
49
+
50
+ ## §schedule-clock The clock
51
+
52
+ No packet carries a clock. The time is told on demand: `discover` with rule
53
+ text in `source` returns one inert candidate whose summary opens with the
54
+ current time in the effective zone (RFC 9557, to the second), states the rule
55
+ in words, and previews its next `PLURNK_SCHEDULE_PREVIEW_OCCURRENCES`
56
+ occurrences ({§schedule-discovery-preview}); the candidate's definition
57
+ carries the canonical rule text. Discovery persists nothing; a `query` or a
58
+ `configuration` is refused.
59
+
60
+ §schedule-discovery-preview `PLURNK_SCHEDULE_PREVIEW_OCCURRENCES` is a family control
61
+ like `PLURNK_SCHEDULE_ENABLED`: a positive integer, never read as a rule alias.
62
+
63
+ ## §schedule-delivery Delivery
64
+
65
+ The module holds one armed occurrence per enabled (workspace, alias). At the
66
+ occurrence it resolves the target worker by name and delivers the prompt
67
+ through the application port's `runLoop` with the source `schedule://<alias>`
68
+ and the definition's policy: the message joins the worker's live loop or
69
+ starts one ({§message-arrival}, {§message-causal-source}); no WAIT holds a loop
70
+ for an occurrence — a loop with nothing live concludes, and the occurrence's
71
+ message starts the next loop on the same worker. The next occurrence
72
+ then arms from the present: a late fire delivers once and skips what it
73
+ missed, never a backlog. A missing worker or a refused delivery disarms the
74
+ rule and lists it `unavailable` with the Problem; `enable` retries. After
75
+ every delivery attempt the family's outcomes are refreshed where the workspace
76
+ is resident.
77
+
78
+ ## §schedule-residency Residency
79
+
80
+ A schedule is an obligation, not a runtime. Cooling a workspace leaves its
81
+ timers armed; `disable` and `remove` disarm through the published rule set. At
82
+ daemon start the module arms every workspace's enabled rules from the
83
+ coordinator's persisted family state ({§functionality-state}) over the
84
+ service definitions, resident or not; an unreadable definition is reported and
85
+ stays disarmed.
86
+
87
+ ## §schedule-environment Environment
88
+
89
+ `PLURNK_SCHEDULE_<ALIAS>` holds one definition as JSON; the alias is the
90
+ suffix case-folded to the family grammar, and two variables folding to one
91
+ alias fail at boot. `PLURNK_SCHEDULE_ENABLED` is the JSON array of aliases a
92
+ workspace starts with; `[]` is the one spelling of none, and an absent or empty
93
+ key is refused by name. A service definition is disable-only in a workspace,
94
+ as for every family. An explicitly empty definition masks that service rule and
95
+ its inherited `ENABLED` selection; it does not remove a workspace-owned rule or
96
+ prohibit adding one. Case-fold collisions still fail validation.
@@ -0,0 +1,102 @@
1
+ import { type FunctionalityCandidate, type FunctionalityDiscoverQuery, type JsonSchema, type ProblemDetails } from "@plurnk/plurnk-contracts";
2
+ import { type ScheduleDefinition } from "./definition.ts";
3
+ import Scheduler, { type SchedulerOptions } from "./Scheduler.ts";
4
+ import ScheduleResources from "./ScheduleResources.ts";
5
+ export declare const SCHEDULE_FAMILY = "schedule";
6
+ export declare const SCHEDULE_OWNER = "@plurnk/plurnk-schedule";
7
+ interface WorkspaceIdentity {
8
+ readonly workspaceId: number;
9
+ }
10
+ interface CallIdentity extends WorkspaceIdentity {
11
+ readonly workerId?: number;
12
+ }
13
+ interface CallOptions {
14
+ readonly env?: Readonly<Record<string, string>>;
15
+ }
16
+ type Outcome = {
17
+ readonly state: "active";
18
+ readonly detail?: object;
19
+ } | {
20
+ readonly state: "unavailable";
21
+ readonly problem: ProblemDetails;
22
+ } | {
23
+ readonly state: "authorization-required";
24
+ readonly authorization: {
25
+ readonly url: string;
26
+ };
27
+ };
28
+ interface Preparation extends WorkspaceIdentity {
29
+ readonly enabled: ReadonlyMap<string, object>;
30
+ readonly previous: unknown | null;
31
+ readonly failure: "publish-unavailable" | "reject";
32
+ readonly force?: string;
33
+ retain(): () => void;
34
+ }
35
+ interface Prepared {
36
+ readonly documents: readonly {
37
+ readonly pathname: string;
38
+ readonly content: string;
39
+ }[];
40
+ readonly outcomes: ReadonlyMap<string, Outcome>;
41
+ readonly snapshot: unknown;
42
+ commit(): Promise<void>;
43
+ abort(): Promise<void>;
44
+ }
45
+ export interface FunctionalityFamilyHandle {
46
+ invoke(verb: "list" | "discover" | "add" | "enable" | "disable" | "remove", params: unknown, identity: WorkspaceIdentity): Promise<{
47
+ readonly status: number;
48
+ readonly body: unknown;
49
+ }>;
50
+ refresh(identity: WorkspaceIdentity): Promise<void>;
51
+ }
52
+ export interface EnvironmentSeam {
53
+ readWorkspaceEnvironment(workspaceId: number): Promise<(ambient?: NodeJS.ProcessEnv) => NodeJS.ProcessEnv>;
54
+ readWorkerEnvironment(workspaceId: number, workerId: number): Promise<(ambient?: NodeJS.ProcessEnv) => NodeJS.ProcessEnv>;
55
+ }
56
+ export interface ScheduleFunctionalityOptions extends Omit<SchedulerOptions, "settled"> {
57
+ }
58
+ export declare class ScheduleFunctionalityError extends Error {
59
+ readonly problem: ProblemDetails;
60
+ constructor(problem: ProblemDetails, cause?: unknown);
61
+ }
62
+ export default class ScheduleFunctionality {
63
+ #private;
64
+ readonly scheme: ScheduleResources;
65
+ readonly family = "schedule";
66
+ readonly namespaceOwner = "@plurnk/plurnk-schedule";
67
+ readonly summary = "Manage scheduled messages";
68
+ readonly definitionSchema: JsonSchema;
69
+ readonly example: {
70
+ alias: string;
71
+ definition: {
72
+ rule: string;
73
+ target: string;
74
+ prompt: string;
75
+ };
76
+ };
77
+ readonly docsDir: string;
78
+ readonly discovery: {
79
+ details: string;
80
+ };
81
+ constructor(env?: NodeJS.ProcessEnv, options?: ScheduleFunctionalityOptions);
82
+ attach(handle: FunctionalityFamilyHandle, environment: EnvironmentSeam): void;
83
+ get scheduler(): Scheduler;
84
+ service(): ReadonlyMap<string, {
85
+ readonly definition: ScheduleDefinition;
86
+ readonly enabled: boolean;
87
+ }>;
88
+ available(): Promise<readonly {
89
+ alias: string;
90
+ definition: object;
91
+ enabled: boolean;
92
+ }[]>;
93
+ discover(query: FunctionalityDiscoverQuery, identity: CallIdentity, options?: CallOptions): Promise<readonly FunctionalityCandidate[]>;
94
+ admit(input: unknown, identity: CallIdentity, _caller?: unknown, options?: CallOptions): Promise<{
95
+ alias: string;
96
+ definition: object;
97
+ }>;
98
+ prepare(preparation: Preparation): Promise<Prepared>;
99
+ teardown(_snapshot: unknown, _identity: WorkspaceIdentity): Promise<void>;
100
+ }
101
+ export {};
102
+ //# sourceMappingURL=Functionality.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Functionality.d.ts","sourceRoot":"","sources":["../src/Functionality.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,242 @@
1
+ // {§schedule-family} — scheduled messages as one workspace Functionality family named `schedule`.
2
+ // The adapter owns the family's truth: the environment's rules, inert discovery that reads a rule
3
+ // and tells the time ({§schedule-clock}), admission that canonicalizes and bounds a rule, and the
4
+ // outcomes the timers stand behind ({§schedule-delivery}, {§schedule-residency}).
5
+ import { fileURLToPath } from "node:url";
6
+ import { Problems, } from "@plurnk/plurnk-contracts";
7
+ import { previewOccurrences, serviceDefinitions, serviceEnabled } from "./config.js";
8
+ import { DEFINITION_SCHEMA, DefinitionError, readDefinition } from "./definition.js";
9
+ import { describeRule, nextOccurrence, normalizeRule, parseRule, ScheduleRuleError, upcoming } from "./rules.js";
10
+ import Scheduler, {} from "./Scheduler.js";
11
+ import { isoString, zoned } from "./temporal.js";
12
+ import ScheduleResources from "./ScheduleResources.js";
13
+ export const SCHEDULE_FAMILY = "schedule";
14
+ export const SCHEDULE_OWNER = "@plurnk/plurnk-schedule";
15
+ export class ScheduleFunctionalityError extends Error {
16
+ problem;
17
+ constructor(problem, cause) {
18
+ super(problem.detail, cause === undefined ? undefined : { cause });
19
+ this.name = "ScheduleFunctionalityError";
20
+ this.problem = problem;
21
+ }
22
+ }
23
+ const problem = (code, status, detail, extensions = {}) => Problems.create("schedule:functionality", code, status, detail, {
24
+ stage: "schedule-functionality",
25
+ ...extensions,
26
+ });
27
+ const failure = (code, status, detail, extensions = {}, cause) => new ScheduleFunctionalityError(problem(code, status, detail, extensions), cause);
28
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
29
+ const messageOf = (cause) => cause instanceof Error ? cause.message : String(cause);
30
+ export default class ScheduleFunctionality {
31
+ scheme;
32
+ family = SCHEDULE_FAMILY;
33
+ namespaceOwner = SCHEDULE_OWNER;
34
+ summary = "Manage scheduled messages";
35
+ definitionSchema = DEFINITION_SCHEMA;
36
+ example = {
37
+ alias: "standup",
38
+ definition: {
39
+ rule: "FREQ=DAILY;BYHOUR=9;BYMINUTE=0;BYSECOND=0;COUNT=20",
40
+ target: "worker://scribe",
41
+ prompt: "Summarize yesterday's log entries for the team.",
42
+ },
43
+ };
44
+ docsDir = fileURLToPath(new URL("..", import.meta.url));
45
+ discovery = {
46
+ details: "`source` is rule text: RFC 5545, an optional DTSTART line and one RRULE line, or bare `FREQ=…` parts. One inert candidate comes back. Its summary opens with the current time in the effective zone and previews the first occurrences; its definition carries the rule as it would be stored, to `add` with a `target` and a `prompt`. Nothing is persisted.",
47
+ };
48
+ #env;
49
+ #scheduler;
50
+ #report;
51
+ // {§schedule-environment} — the service's rules, canonical from construction: a rule without a
52
+ // DTSTART starts when it is read, and a service rule is read once, when the daemon starts.
53
+ #service;
54
+ #handle = null;
55
+ #environment = null;
56
+ constructor(env = process.env, options = {}) {
57
+ this.#env = env;
58
+ this.#report = options.report ?? ((message, cause) => { console.error(`${message}:`, cause); });
59
+ this.#scheduler = new Scheduler({ ...options, report: this.#report, settled: (workspaceId) => { this.#settled(workspaceId); } });
60
+ this.scheme = new ScheduleResources(async (workspaceId) => {
61
+ if (this.#handle === null)
62
+ throw new Error("schedule family is not attached");
63
+ const listing = await this.#handle.invoke("list", {}, { workspaceId });
64
+ return listing.body.definitions;
65
+ });
66
+ const zone = env.TZ;
67
+ if (zone === undefined || zone.length === 0)
68
+ throw new Error("TZ is unset; @plurnk/plurnk-schedule declares its default in .env.defaults.");
69
+ const enabled = serviceEnabled(env);
70
+ const now = this.#scheduler.now();
71
+ this.#service = new Map([...serviceDefinitions(env)].map(([alias, definition]) => {
72
+ let parsed;
73
+ try {
74
+ parsed = normalizeRule(definition.rule, zone, now);
75
+ }
76
+ catch (cause) {
77
+ throw new Error(`PLURNK_SCHEDULE_${alias.toUpperCase()}: ${messageOf(cause)}`, { cause });
78
+ }
79
+ return [alias, { definition: { ...definition, rule: parsed.text }, enabled: enabled.has(alias) }];
80
+ }));
81
+ }
82
+ attach(handle, environment) {
83
+ this.#handle = handle;
84
+ this.#environment = environment;
85
+ }
86
+ get scheduler() {
87
+ return this.#scheduler;
88
+ }
89
+ // The service's rules as the coordinator's baseline, and as the module re-arms them at start.
90
+ service() {
91
+ return this.#service;
92
+ }
93
+ async available() {
94
+ return [...this.#service].map(([alias, { definition, enabled }]) => ({ alias, definition, enabled }));
95
+ }
96
+ // {§schedule-clock} — the one place the time is told: on demand, beside the rule it reads.
97
+ async discover(query, identity, options) {
98
+ if (query.configuration !== undefined) {
99
+ throw failure("configuration-unsupported", 400, "schedule discovery reads rule text from `source`; it takes no configuration.", { retryable: false });
100
+ }
101
+ if (query.query !== undefined) {
102
+ throw failure("query-unsupported", 400, "schedule discovery reads rule text from `source`; there is no catalog to search.", { retryable: false });
103
+ }
104
+ if (query.source === undefined) {
105
+ throw failure("source-required", 400, "schedule discovery needs rule text in `source`; `FREQ=DAILY` is enough to read the time.", { retryable: false });
106
+ }
107
+ const zone = await this.#zone(identity, options);
108
+ const now = this.#scheduler.now();
109
+ const parsed = this.#read(query.source, zone, now);
110
+ const preview = upcoming(parsed, now, previewOccurrences(this.#env)).map(isoString);
111
+ return [{
112
+ alias: parsed.rule.options().freq.toLowerCase(),
113
+ summary: [
114
+ `now ${isoString(zoned(now, zone))}`,
115
+ describeRule(parsed),
116
+ preview.length === 0 ? "no occurrence ahead" : `next ${preview.join(", ")}`,
117
+ ...(parsed.bounded ? [] : ["unbounded: add needs COUNT or UNTIL"]),
118
+ ].join("; "),
119
+ definition: { rule: parsed.text },
120
+ provenance: { kind: "rule", source: query.source },
121
+ }];
122
+ }
123
+ async admit(input, identity, _caller, options) {
124
+ const params = isRecord(input) ? input : {};
125
+ if (typeof params.alias !== "string")
126
+ throw failure("alias-required", 400, "schedule add needs an alias.", { retryable: false });
127
+ let definition;
128
+ try {
129
+ definition = readDefinition(params.definition);
130
+ }
131
+ catch (cause) {
132
+ if (!(cause instanceof DefinitionError))
133
+ throw cause;
134
+ throw failure("definition-invalid", 400, "The schedule definition is invalid.", { errors: cause.errors, retryable: false }, cause);
135
+ }
136
+ const parsed = this.#read(definition.rule, await this.#zone(identity, options), this.#scheduler.now());
137
+ // {§schedule-bound}
138
+ if (!parsed.bounded)
139
+ throw failure("rule-unbounded", 400, "A workspace rule ends: give the RRULE a COUNT or an UNTIL.", { retryable: false });
140
+ return { alias: params.alias, definition: { ...definition, rule: parsed.text } };
141
+ }
142
+ async prepare(preparation) {
143
+ const { workspaceId } = preparation;
144
+ const previous = preparation.previous;
145
+ const rules = new Map();
146
+ const unavailable = new Map();
147
+ const outcomes = new Map();
148
+ const now = this.#scheduler.now();
149
+ for (const [alias, raw] of preparation.enabled) {
150
+ if (preparation.force === alias)
151
+ this.#scheduler.forgive(workspaceId, alias);
152
+ let definition;
153
+ let parsed;
154
+ try {
155
+ definition = readDefinition(raw);
156
+ parsed = parseRule(definition.rule);
157
+ }
158
+ catch (cause) {
159
+ const text = JSON.stringify(raw);
160
+ const fresh = previous?.unavailable.get(alias) !== text;
161
+ const unreadable = problem("rule-invalid", 400, `The rule of '${alias}' is unreadable: ${messageOf(cause)}`, { alias, retryable: false });
162
+ if (preparation.failure === "reject" && fresh)
163
+ throw new ScheduleFunctionalityError(unreadable, cause);
164
+ if (fresh)
165
+ this.#report(`schedule '${alias}' unavailable in workspace ${workspaceId}`, cause);
166
+ unavailable.set(alias, text);
167
+ outcomes.set(alias, { state: "unavailable", problem: unreadable });
168
+ continue;
169
+ }
170
+ rules.set(alias, { alias, definition, parsed });
171
+ const failed = this.#scheduler.failure(workspaceId, alias);
172
+ if (failed !== undefined && failed.text === parsed.text) {
173
+ unavailable.set(alias, parsed.text);
174
+ outcomes.set(alias, { state: "unavailable", problem: structuredClone(failed.problem) });
175
+ continue;
176
+ }
177
+ const next = nextOccurrence(parsed, now);
178
+ outcomes.set(alias, {
179
+ state: "active",
180
+ detail: {
181
+ path: `schedule:///rules/${encodeURIComponent(alias)}`,
182
+ rule: parsed.text,
183
+ zone: parsed.zone,
184
+ text: describeRule(parsed),
185
+ next: next === null ? null : isoString(next),
186
+ exhausted: next === null,
187
+ target: definition.target,
188
+ ...(definition.policy === undefined ? {} : { policy: definition.policy }),
189
+ },
190
+ });
191
+ }
192
+ const snapshot = { rules, unavailable };
193
+ return {
194
+ documents: [],
195
+ outcomes,
196
+ snapshot,
197
+ commit: async () => { await this.#scheduler.sync(workspaceId, rules); },
198
+ abort: async () => { },
199
+ };
200
+ }
201
+ // {§schedule-residency} — a schedule is an obligation, not a runtime: cooling the workspace
202
+ // leaves its timers armed.
203
+ async teardown(_snapshot, _identity) { }
204
+ // {§schedule-zone} — the zone a rule is read in: the call's own env when it carries one (a
205
+ // worker's `env` metadata), else the invoking Worker's environment (its overrides over the
206
+ // workspace layer over the service's), else the workspace's.
207
+ async #zone(identity, options) {
208
+ const called = options?.env?.TZ;
209
+ if (called !== undefined && called.length > 0)
210
+ return called;
211
+ const environment = this.#environment === null
212
+ ? this.#env
213
+ : identity.workerId === undefined
214
+ ? (await this.#environment.readWorkspaceEnvironment(identity.workspaceId))(this.#env)
215
+ : (await this.#environment.readWorkerEnvironment(identity.workspaceId, identity.workerId))(this.#env);
216
+ const zone = environment.TZ;
217
+ if (zone === undefined || zone.length === 0)
218
+ throw new Error("TZ is unset; @plurnk/plurnk-schedule declares its default in .env.defaults.");
219
+ return zone;
220
+ }
221
+ #read(text, zone, now) {
222
+ try {
223
+ return normalizeRule(text, zone, now);
224
+ }
225
+ catch (cause) {
226
+ if (!(cause instanceof ScheduleRuleError))
227
+ throw cause;
228
+ throw failure(cause.code, 400, cause.message, { retryable: false }, cause);
229
+ }
230
+ }
231
+ // A delivery settled: the outcomes name the next occurrence or the failure. A workspace that
232
+ // is not resident has nothing published; the coordinator's refresh returns at once.
233
+ #settled(workspaceId) {
234
+ const handle = this.#handle;
235
+ if (handle === null)
236
+ return;
237
+ void handle.refresh({ workspaceId }).catch((cause) => {
238
+ this.#report(`schedule outcomes did not refresh for workspace ${workspaceId}`, cause);
239
+ });
240
+ }
241
+ }
242
+ //# sourceMappingURL=Functionality.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Functionality.js","sourceRoot":"","sources":["../src/Functionality.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,kGAAkG;AAClG,kGAAkG;AAClG,kFAAkF;AAClF,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EACH,QAAQ,GAKX,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AACrF,OAAO,EAAE,iBAAiB,EAAE,eAAe,EAAE,cAAc,EAA2B,MAAM,iBAAiB,CAAC;AAC9G,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,aAAa,EAAE,SAAS,EAAE,iBAAiB,EAAE,QAAQ,EAAmB,MAAM,YAAY,CAAC;AAClI,OAAO,SAAS,EAAE,EAA6C,MAAM,gBAAgB,CAAC;AACtF,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,iBAAiB,MAAM,wBAAwB,CAAC;AAEvD,MAAM,CAAC,MAAM,eAAe,GAAG,UAAU,CAAC;AAC1C,MAAM,CAAC,MAAM,cAAc,GAAG,yBAAyB,CAAC;AA2DxD,MAAM,OAAO,0BAA2B,SAAQ,KAAK;IACxC,OAAO,CAAiB;IAEjC,YAAY,OAAuB,EAAE,KAAe;QAChD,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC;QACnE,IAAI,CAAC,IAAI,GAAG,4BAA4B,CAAC;QACzC,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC;IAC3B,CAAC;CACJ;AAED,MAAM,OAAO,GAAG,CACZ,IAAY,EACZ,MAAc,EACd,MAAc,EACd,UAAU,GAAsC,EAAE,EACpC,EAAE,CAAC,QAAQ,CAAC,MAAM,CAAC,wBAAwB,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE;IACjF,KAAK,EAAE,wBAAwB;IAC/B,GAAG,UAAU;CAChB,CAAC,CAAC;AAEH,MAAM,OAAO,GAAG,CACZ,IAAY,EACZ,MAAc,EACd,MAAc,EACd,UAAU,GAAsC,EAAE,EAClD,KAAe,EACW,EAAE,CAAC,IAAI,0BAA0B,CAAC,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,CAAC,EAAE,KAAK,CAAC,CAAC;AAElH,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAoC,EAAE,CAClE,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;AAEzE,MAAM,SAAS,GAAG,CAAC,KAAc,EAAU,EAAE,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;AAErG,MAAM,CAAC,OAAO,OAAO,qBAAqB;IAC7B,MAAM,CAAoB;IAC1B,MAAM,GAAG,eAAe,CAAC;IACzB,cAAc,GAAG,cAAc,CAAC;IAChC,OAAO,GAAG,2BAA2B,CAAC;IACtC,gBAAgB,GAAe,iBAAiB,CAAC;IACjD,OAAO,GAAG;QACf,KAAK,EAAE,SAAS;QAChB,UAAU,EAAE;YACR,IAAI,EAAE,oDAAoD;YAC1D,MAAM,EAAE,iBAAiB;YACzB,MAAM,EAAE,iDAAiD;SAC5D;KACJ,CAAC;IACO,OAAO,GAAG,aAAa,CAAC,IAAI,GAAG,CAAC,IAAI,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACxD,SAAS,GAAG;QACjB,OAAO,EAAE,+VAA+V;KAC3W,CAAC;IAEO,IAAI,CAAoB;IACxB,UAAU,CAAY;IACtB,OAAO,CAA4C;IAC5D,+FAA+F;IAC/F,2FAA2F;IAClF,QAAQ,CAA8F;IAC/G,OAAO,GAAqC,IAAI,CAAC;IACjD,YAAY,GAA2B,IAAI,CAAC;IAE5C,YAAY,GAAG,GAAsB,OAAO,CAAC,GAAG,EAAE,OAAO,GAAiC,EAAE;QACxF,IAAI,CAAC,IAAI,GAAG,GAAG,CAAC;QAChB,IAAI,CAAC,OAAO,GAAG,OAAO,CAAC,MAAM,IAAI,CAAC,CAAC,OAAO,EAAE,KAAK,EAAE,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,OAAO,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAChG,IAAI,CAAC,UAAU,GAAG,IAAI,SAAS,CAAC,EAAE,GAAG,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC,WAAW,EAAE,EAAE,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACjI,IAAI,CAAC,MAAM,GAAG,IAAI,iBAAiB,CAAC,KAAK,EAAE,WAAW,EAAE,EAAE;YACtD,IAAI,IAAI,CAAC,OAAO,KAAK,IAAI;gBAAE,MAAM,IAAI,KAAK,CAAC,iCAAiC,CAAC,CAAC;YAC9E,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,EAAE,EAAE,EAAE,WAAW,EAAE,CAAC,CAAC;YACvE,OAAQ,OAAO,CAAC,IAAiE,CAAC,WAAW,CAAC;QAClG,CAAC,CAAC,CAAC;QACH,MAAM,IAAI,GAAG,GAAG,CAAC,EAAE,CAAC;QACpB,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,6EAA6E,CAAC,CAAC;QAC5I,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;QACpC,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC;QAClC,IAAI,CAAC,QAAQ,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,UAAU,CAAC,EAAE,EAAE;YAC7E,IAAI,MAAkB,CAAC;YACvB,IAAI,CAAC;gBACD,MAAM,GAAG,aAAa,CAAC,UAAU,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;YACvD,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACb,MAAM,IAAI,KAAK,CAAC,mBAAmB,KAAK,CAAC,WAAW,EAAE,KAAK,SAAS,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;YAC9F,CAAC;YACD,OAAO,CAAC,KAAK,EAAE,EAAE,UAAU,EAAE,EAAE,GAAG,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;QACtG,CAAC,CAAC,CAAC,CAAC;IACR,CAAC;IAED,MAAM,CAAC,MAAiC,EAAE,WAA4B;QAClE,IAAI,CAAC,OAAO,GAAG,MAAM,CAAC;QACtB,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC;IACpC,CAAC;IAED,IAAI,SAAS;QACT,OAAO,IAAI,CAAC,UAAU,CAAC;IAC3B,CAAC;IAED,8FAA8F;IAC9F,OAAO;QACH,OAAO,IAAI,CAAC,QAAQ,CAAC;IACzB,CAAC;IAED,KAAK,CAAC,SAAS;QACX,OAAO,CAAC,GAAG,IAAI,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;IAC1G,CAAC;IAED,2FAA2F;IAC3F,KAAK,CAAC,QAAQ,CAAC,KAAiC,EAAE,QAAsB,EAAE,OAAqB;QAC3F,IAAI,KAAK,CAAC,aAAa,KAAK,SAAS,EAAE,CAAC;YACpC,MAAM,OAAO,CAAC,2BAA2B,EAAE,GAAG,EAAE,8EAA8E,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QAC1J,CAAC;QACD,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;YAC5B,MAAM,OAAO,CAAC,mBAAmB,EAAE,GAAG,EAAE,kFAAkF,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QACtJ,CAAC;QACD,IAAI,KAAK,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;YAC7B,MAAM,OAAO,CAAC,iBAAiB,EAAE,GAAG,EAAE,0FAA0F,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QAC5J,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QACjD,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC;QAClC,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;QACnD,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,EAAE,GAAG,EAAE,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC;QACpF,OAAO,CAAC;gBACJ,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE;gBAC/C,OAAO,EAAE;oBACL,OAAO,SAAS,CAAC,KAAK,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC,EAAE;oBACpC,YAAY,CAAC,MAAM,CAAC;oBACpB,OAAO,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,QAAQ,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;oBAC3E,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,qCAAqC,CAAC,CAAC;iBACrE,CAAC,IAAI,CAAC,IAAI,CAAC;gBACZ,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE;gBACjC,UAAU,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE;aACrD,CAAC,CAAC;IACP,CAAC;IAED,KAAK,CAAC,KAAK,CAAC,KAAc,EAAE,QAAsB,EAAE,OAAiB,EAAE,OAAqB;QACxF,MAAM,MAAM,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;QAC5C,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ;YAAE,MAAM,OAAO,CAAC,gBAAgB,EAAE,GAAG,EAAE,8BAA8B,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QACjI,IAAI,UAA8B,CAAC;QACnC,IAAI,CAAC;YACD,UAAU,GAAG,cAAc,CAAC,MAAM,CAAC,UAAU,CAAC,CAAC;QACnD,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,IAAI,CAAC,CAAC,KAAK,YAAY,eAAe,CAAC;gBAAE,MAAM,KAAK,CAAC;YACrD,MAAM,OAAO,CAAC,oBAAoB,EAAE,GAAG,EAAE,qCAAqC,EAAE,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,KAAK,CAAC,CAAC;QACvI,CAAC;QACD,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,EAAE,MAAM,IAAI,CAAC,KAAK,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC,CAAC;QACvG,oBAAoB;QACpB,IAAI,CAAC,MAAM,CAAC,OAAO;YAAE,MAAM,OAAO,CAAC,gBAAgB,EAAE,GAAG,EAAE,4DAA4D,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;QAC9I,OAAO,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,GAAG,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,EAAE,CAAC;IACrF,CAAC;IAED,KAAK,CAAC,OAAO,CAAC,WAAwB;QAClC,MAAM,EAAE,WAAW,EAAE,GAAG,WAAW,CAAC;QACpC,MAAM,QAAQ,GAAG,WAAW,CAAC,QAA2B,CAAC;QACzD,MAAM,KAAK,GAAG,IAAI,GAAG,EAAyB,CAAC;QAC/C,MAAM,WAAW,GAAG,IAAI,GAAG,EAAkB,CAAC;QAC9C,MAAM,QAAQ,GAAG,IAAI,GAAG,EAAmB,CAAC;QAC5C,MAAM,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC,GAAG,EAAE,CAAC;QAClC,KAAK,MAAM,CAAC,KAAK,EAAE,GAAG,CAAC,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;YAC7C,IAAI,WAAW,CAAC,KAAK,KAAK,KAAK;gBAAE,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;YAC7E,IAAI,UAA8B,CAAC;YACnC,IAAI,MAAkB,CAAC;YACvB,IAAI,CAAC;gBACD,UAAU,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC;gBACjC,MAAM,GAAG,SAAS,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YACxC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACb,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;gBACjC,MAAM,KAAK,GAAG,QAAQ,EAAE,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC;gBACxD,MAAM,UAAU,GAAG,OAAO,CAAC,cAAc,EAAE,GAAG,EAAE,gBAAgB,KAAK,oBAAoB,SAAS,CAAC,KAAK,CAAC,EAAE,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,CAAC,CAAC;gBAC1I,IAAI,WAAW,CAAC,OAAO,KAAK,QAAQ,IAAI,KAAK;oBAAE,MAAM,IAAI,0BAA0B,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC;gBACvG,IAAI,KAAK;oBAAE,IAAI,CAAC,OAAO,CAAC,aAAa,KAAK,8BAA8B,WAAW,EAAE,EAAE,KAAK,CAAC,CAAC;gBAC9F,WAAW,CAAC,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,CAAC;gBAC7B,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,UAAU,EAAE,CAAC,CAAC;gBACnE,SAAS;YACb,CAAC;YACD,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,CAAC,CAAC;YAChD,MAAM,MAAM,GAAG,IAAI,CAAC,UAAU,CAAC,OAAO,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC;YAC3D,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC;gBACtD,WAAW,CAAC,GAAG,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,CAAC,CAAC;gBACpC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,eAAe,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC;gBACxF,SAAS;YACb,CAAC;YACD,MAAM,IAAI,GAAG,cAAc,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;YACzC,QAAQ,CAAC,GAAG,CAAC,KAAK,EAAE;gBAChB,KAAK,EAAE,QAAQ;gBACf,MAAM,EAAE;oBACJ,IAAI,EAAE,qBAAqB,kBAAkB,CAAC,KAAK,CAAC,EAAE;oBACtD,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,IAAI,EAAE,MAAM,CAAC,IAAI;oBACjB,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC;oBAC1B,IAAI,EAAE,IAAI,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS,CAAC,IAAI,CAAC;oBAC5C,SAAS,EAAE,IAAI,KAAK,IAAI;oBACxB,MAAM,EAAE,UAAU,CAAC,MAAM;oBACzB,GAAG,CAAC,UAAU,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC;iBAC5E;aACJ,CAAC,CAAC;QACP,CAAC;QACD,MAAM,QAAQ,GAAa,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC;QAClD,OAAO;YACH,SAAS,EAAE,EAAE;YACb,QAAQ;YACR,QAAQ;YACR,MAAM,EAAE,KAAK,IAAI,EAAE,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;YACvE,KAAK,EAAE,KAAK,IAAI,EAAE,GAAE,CAAC;SACxB,CAAC;IACN,CAAC;IAED,4FAA4F;IAC5F,2BAA2B;IAC3B,KAAK,CAAC,QAAQ,CAAC,SAAkB,EAAE,SAA4B,IAAkB,CAAC;IAElF,2FAA2F;IAC3F,2FAA2F;IAC3F,6DAA6D;IAC7D,KAAK,CAAC,KAAK,CAAC,QAAsB,EAAE,OAAqB;QACrD,MAAM,MAAM,GAAG,OAAO,EAAE,GAAG,EAAE,EAAE,CAAC;QAChC,IAAI,MAAM,KAAK,SAAS,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,MAAM,CAAC;QAC7D,MAAM,WAAW,GAAG,IAAI,CAAC,YAAY,KAAK,IAAI;YAC1C,CAAC,CAAC,IAAI,CAAC,IAAI;YACX,CAAC,CAAC,QAAQ,CAAC,QAAQ,KAAK,SAAS;gBAC7B,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,YAAY,CAAC,wBAAwB,CAAC,QAAQ,CAAC,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;gBACrF,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,YAAY,CAAC,qBAAqB,CAAC,QAAQ,CAAC,WAAW,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC9G,MAAM,IAAI,GAAG,WAAW,CAAC,EAAE,CAAC;QAC5B,IAAI,IAAI,KAAK,SAAS,IAAI,IAAI,CAAC,MAAM,KAAK,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,6EAA6E,CAAC,CAAC;QAC5I,OAAO,IAAI,CAAC;IAChB,CAAC;IAED,KAAK,CAAC,IAAY,EAAE,IAAY,EAAE,GAAW;QACzC,IAAI,CAAC;YACD,OAAO,aAAa,CAAC,IAAI,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC;QAC1C,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACb,IAAI,CAAC,CAAC,KAAK,YAAY,iBAAiB,CAAC;gBAAE,MAAM,KAAK,CAAC;YACvD,MAAM,OAAO,CAAC,KAAK,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,EAAE,KAAK,CAAC,CAAC;QAC/E,CAAC;IACL,CAAC;IAED,6FAA6F;IAC7F,oFAAoF;IACpF,QAAQ,CAAC,WAAmB;QACxB,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC;QAC5B,IAAI,MAAM,KAAK,IAAI;YAAE,OAAO;QAC5B,KAAK,MAAM,CAAC,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;YAC1D,IAAI,CAAC,OAAO,CAAC,mDAAmD,WAAW,EAAE,EAAE,KAAK,CAAC,CAAC;QAC1F,CAAC,CAAC,CAAC;IACP,CAAC;CACJ"}
@@ -0,0 +1,21 @@
1
+ import type { ApplicationPort } from "@plurnk/plurnk-contracts";
2
+ import ScheduleFunctionality, { type EnvironmentSeam, type FunctionalityFamilyHandle, type ScheduleFunctionalityOptions } from "./Functionality.ts";
3
+ interface SetupSeam extends EnvironmentSeam {
4
+ registerFunctionalityAdapter(adapter: ScheduleFunctionality): FunctionalityFamilyHandle;
5
+ readWorkspaceModuleState(workspaceId: number, namespaceOwner: string): Promise<unknown | null>;
6
+ }
7
+ type StartSeam = Pick<ApplicationPort, "listWorkspaces" | "listWorkers" | "runLoop">;
8
+ export interface ModuleOptions extends ScheduleFunctionalityOptions {
9
+ readonly env?: NodeJS.ProcessEnv;
10
+ }
11
+ export default class Module {
12
+ #private;
13
+ static init(options?: ModuleOptions): Module;
14
+ private constructor();
15
+ get functionality(): ScheduleFunctionality;
16
+ setup(seam: SetupSeam): void;
17
+ start(seam: StartSeam): Promise<void>;
18
+ close(): Promise<void>;
19
+ }
20
+ export {};
21
+ //# sourceMappingURL=Module.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Module.d.ts","sourceRoot":"","sources":["../src/Module.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAEhE,OAAO,qBAAqB,EAAE,EAE1B,KAAK,eAAe,EACpB,KAAK,yBAAyB,EAC9B,KAAK,4BAA4B,EACpC,MAAM,oBAAoB,CAAC;AAI5B,UAAU,SAAU,SAAQ,eAAe;IACvC,4BAA4B,CAAC,OAAO,EAAE,qBAAqB,GAAG,yBAAyB,CAAC;IACxF,wBAAwB,CAAC,WAAW,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC;CAClG;AAED,KAAK,SAAS,GAAG,IAAI,CAAC,eAAe,EAAE,gBAAgB,GAAG,aAAa,GAAG,SAAS,CAAC,CAAC;AAarF,MAAM,WAAW,aAAc,SAAQ,4BAA4B;IAC/D,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,UAAU,CAAC;CACpC;AAKD,MAAM,CAAC,OAAO,OAAO,MAAM;;IAOvB,MAAM,CAAC,IAAI,CAAC,OAAO,GAAE,aAAkB,GAAG,MAAM,CAE/C;IAED,OAAO,eAKN;IAED,IAAI,aAAa,IAAI,qBAAqB,CAEzC;IAED,KAAK,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAI3B;IAEK,KAAK,CAAC,IAAI,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CA+B1C;IAEK,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAE3B;CASJ"}
package/dist/Module.js ADDED
@@ -0,0 +1,79 @@
1
+ import { readDefinition } from "./definition.js";
2
+ import ScheduleFunctionality, { SCHEDULE_OWNER, } from "./Functionality.js";
3
+ import { parseRule } from "./rules.js";
4
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
5
+ export default class Module {
6
+ #env;
7
+ #functionality;
8
+ #report;
9
+ #seam = null;
10
+ #started = false;
11
+ static init(options = {}) {
12
+ return new Module(options);
13
+ }
14
+ constructor(options) {
15
+ const { env, ...functionality } = options;
16
+ this.#env = env ?? process.env;
17
+ this.#report = options.report ?? ((message, cause) => { console.error(`${message}:`, cause); });
18
+ this.#functionality = new ScheduleFunctionality(this.#env, functionality);
19
+ }
20
+ get functionality() {
21
+ return this.#functionality;
22
+ }
23
+ setup(seam) {
24
+ if (this.#seam !== null)
25
+ throw new Error("schedule module already set up");
26
+ this.#seam = seam;
27
+ this.#functionality.attach(seam.registerFunctionalityAdapter(this.#functionality), seam);
28
+ }
29
+ async start(seam) {
30
+ const setup = this.#seam;
31
+ if (setup === null)
32
+ throw new Error("schedule module started before setup");
33
+ if (this.#started)
34
+ throw new Error("schedule module already started");
35
+ this.#started = true;
36
+ this.#functionality.scheduler.start(seam);
37
+ const service = this.#functionality.service();
38
+ for (const workspace of await seam.listWorkspaces()) {
39
+ const state = Module.#state(await setup.readWorkspaceModuleState(workspace.id, SCHEDULE_OWNER));
40
+ const effective = new Map();
41
+ for (const [alias, { definition, enabled }] of service)
42
+ effective.set(alias, { definition, enabled });
43
+ for (const [alias, record] of Object.entries(state.definitions)) {
44
+ if (record.origin !== "service") {
45
+ effective.set(alias, { definition: record.definition, enabled: record.enabled });
46
+ continue;
47
+ }
48
+ const base = effective.get(alias);
49
+ if (base !== undefined)
50
+ effective.set(alias, { ...base, enabled: record.enabled });
51
+ }
52
+ const rules = new Map();
53
+ for (const [alias, { definition, enabled }] of effective) {
54
+ if (!enabled)
55
+ continue;
56
+ try {
57
+ const read = readDefinition(definition);
58
+ rules.set(alias, { alias, definition: read, parsed: parseRule(read.rule) });
59
+ }
60
+ catch (cause) {
61
+ this.#report(`schedule '${alias}' in workspace ${workspace.id} is unreadable and stays disarmed`, cause);
62
+ }
63
+ }
64
+ await this.#functionality.scheduler.sync(workspace.id, rules);
65
+ }
66
+ }
67
+ async close() {
68
+ await this.#functionality.scheduler.close();
69
+ }
70
+ static #state(raw) {
71
+ if (raw === null)
72
+ return { version: 1, definitions: {} };
73
+ if (!isRecord(raw) || raw.version !== 1 || !isRecord(raw.definitions)) {
74
+ throw new Error("schedule state in workspace_module_state is not a version-1 family state");
75
+ }
76
+ return raw;
77
+ }
78
+ }
79
+ //# sourceMappingURL=Module.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Module.js","sourceRoot":"","sources":["../src/Module.ts"],"names":[],"mappings":"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"}