bree-plugin-one-time 1.0.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.
Files changed (4) hide show
  1. package/README.md +77 -0
  2. package/index.d.ts +12 -0
  3. package/index.js +152 -0
  4. package/package.json +32 -0
package/README.md ADDED
@@ -0,0 +1,77 @@
1
+ # bree-plugin-one-time
2
+
3
+ Persistent one-time job scheduling for [Bree](https://github.com/breejs/bree). Jobs added with a `date` property are automatically saved to a JSON queue file and restored on scheduler restart — so they survive process restarts.
4
+
5
+ ## The problem
6
+
7
+ Bree natively supports one-time jobs via the `date` property, but they only live in memory. If your scheduler process restarts before the job fires, the job is silently lost.
8
+
9
+ ## The solution
10
+
11
+ This plugin wraps `Bree.prototype.add` to detect date-based jobs and persist them to a local JSON file. On `init`, it reads the file and re-registers any jobs that haven't fired yet. On job completion, the entry is removed from the file automatically.
12
+
13
+ ## Install
14
+
15
+ ```sh
16
+ npm install bree-plugin-one-time
17
+ ```
18
+
19
+ ## Usage
20
+
21
+ ```js
22
+ const Bree = require('bree');
23
+ const oneTime = require('bree-plugin-one-time');
24
+
25
+ Bree.extend(oneTime, {
26
+ queueFile: './jobs/one-time-queue.json', // optional, default: cwd/.bree-one-time-queue.json
27
+ verbose: true, // optional, default: false
28
+ });
29
+
30
+ const bree = new Bree({ jobs: [] });
31
+ await bree.init();
32
+ await bree.start();
33
+
34
+ // Schedule a one-time job — automatically persisted
35
+ bree.add({
36
+ name: 'send-flight-reminder',
37
+ path: './jobs/discord-ping.cjs',
38
+ date: new Date('2026-04-12T13:17:00-04:00'),
39
+ env: {
40
+ DISCORD_CHANNEL_ID: '1234567890',
41
+ DISCORD_USER_ID: '279033205628207104',
42
+ MESSAGE: 'Check in to AA 5838 SAV → PHL — confirmation KJEVBS',
43
+ },
44
+ });
45
+ ```
46
+
47
+ If the scheduler restarts before `2026-04-12T13:17:00`, the job is restored from the queue file and will still fire on time.
48
+
49
+ ## Options
50
+
51
+ | Option | Type | Default | Description |
52
+ |--------|------|---------|-------------|
53
+ | `queueFile` | `string` | `cwd/.bree-one-time-queue.json` | Path to the JSON queue file |
54
+ | `verbose` | `boolean` | `false` | Log queue operations to console |
55
+
56
+ ## How it works
57
+
58
+ 1. **`add`** — wraps `Bree.prototype.add`. Any job with a `date` property is written to the queue file before being passed to Bree.
59
+ 2. **`init`** — wraps `Bree.prototype.init`. After initialization, reads the queue file, skips expired jobs, re-registers future jobs with Bree.
60
+ 3. **`start`** — wraps `Bree.prototype.start`. Attaches a `worker deleted` listener to remove completed jobs from the queue file.
61
+
62
+ ## Queue file format
63
+
64
+ ```json
65
+ [
66
+ {
67
+ "name": "send-flight-reminder",
68
+ "path": "./jobs/discord-ping.cjs",
69
+ "date": "2026-04-12T17:17:00.000Z",
70
+ "worker": null,
71
+ "env": {
72
+ "DISCORD_CHANNEL_ID": "1234567890",
73
+ "MESSAGE": "Check in to your flight!"
74
+ }
75
+ }
76
+ ]
77
+ ```
package/index.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import Bree from 'bree';
2
+
3
+ export interface OneTimePluginOptions {
4
+ /** Path to the JSON queue file. Default: cwd/.bree-one-time-queue.json */
5
+ queueFile?: string;
6
+ /** Log queue operations to console. Default: false */
7
+ verbose?: boolean;
8
+ }
9
+
10
+ declare function oneTimePlugin(options: OneTimePluginOptions, Bree: typeof Bree): void;
11
+
12
+ export = oneTimePlugin;
package/index.js ADDED
@@ -0,0 +1,152 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ /**
7
+ * bree-plugin-one-time
8
+ *
9
+ * Persistent one-time job scheduling for Bree. Jobs added with a `date`
10
+ * property are automatically saved to a JSON queue file and restored on
11
+ * scheduler restart, so they survive process restarts.
12
+ *
13
+ * Usage:
14
+ * const Bree = require('bree');
15
+ * const oneTime = require('bree-plugin-one-time');
16
+ * Bree.extend(oneTime, { queueFile: './one-time-queue.json' });
17
+ *
18
+ * Then add jobs as normal — any job with a `date` property is auto-persisted:
19
+ * bree.add({ name: 'my-job', path: './jobs/my-job.cjs', date: new Date('2026-04-12T13:17:00-04:00') });
20
+ *
21
+ * Options:
22
+ * Bree.extend(oneTime, {
23
+ * queueFile: './one-time-queue.json', // Path to queue file (default: cwd/.bree-one-time-queue.json)
24
+ * verbose: false, // Log queue operations (default: false)
25
+ * });
26
+ */
27
+
28
+ module.exports = function oneTimePlugin(options, Bree) {
29
+ const opts = {
30
+ queueFile: path.join(process.cwd(), '.bree-one-time-queue.json'),
31
+ verbose: false,
32
+ ...options
33
+ };
34
+
35
+ // ── Queue persistence helpers ──────────────────────────────────────────────
36
+
37
+ function loadQueue() {
38
+ try {
39
+ const raw = fs.readFileSync(opts.queueFile, 'utf8');
40
+ return JSON.parse(raw);
41
+ } catch {
42
+ return [];
43
+ }
44
+ }
45
+
46
+ function saveQueue(queue) {
47
+ fs.writeFileSync(opts.queueFile, JSON.stringify(queue, null, 2));
48
+ }
49
+
50
+ function addToQueue(job) {
51
+ const queue = loadQueue().filter(j => j.name !== job.name);
52
+ queue.push({
53
+ name: job.name,
54
+ path: job.path || null,
55
+ date: job.date instanceof Date ? job.date.toISOString() : job.date,
56
+ worker: job.worker || null,
57
+ env: job.env || null,
58
+ });
59
+ saveQueue(queue);
60
+ if (opts.verbose) console.log(`[bree-plugin-one-time] queued: ${job.name} at ${job.date}`);
61
+ }
62
+
63
+ function removeFromQueue(name) {
64
+ const queue = loadQueue().filter(j => j.name !== name);
65
+ saveQueue(queue);
66
+ if (opts.verbose) console.log(`[bree-plugin-one-time] dequeued: ${name}`);
67
+ }
68
+
69
+ // ── Wrap Bree.prototype.add ────────────────────────────────────────────────
70
+ // Intercept jobs with a `date` property and persist them before adding to Bree.
71
+
72
+ const originalAdd = Bree.prototype.add;
73
+
74
+ Bree.prototype.add = function (jobs) {
75
+ const jobsArr = Array.isArray(jobs) ? jobs : [jobs];
76
+
77
+ for (const job of jobsArr) {
78
+ if (job && typeof job === 'object' && job.date) {
79
+ addToQueue(job);
80
+ }
81
+ }
82
+
83
+ return originalAdd.call(this, jobs);
84
+ };
85
+
86
+ // ── Wrap Bree.prototype.init ───────────────────────────────────────────────
87
+ // On startup, restore any persisted jobs that haven't fired yet.
88
+
89
+ const originalInit = Bree.prototype.init;
90
+
91
+ Bree.prototype.init = async function () {
92
+ const result = await originalInit.call(this);
93
+
94
+ const queue = loadQueue();
95
+ const now = Date.now();
96
+ const stillValid = [];
97
+
98
+ for (const job of queue) {
99
+ const fireAt = new Date(job.date).getTime();
100
+
101
+ if (isNaN(fireAt)) {
102
+ if (opts.verbose) console.log(`[bree-plugin-one-time] invalid date, skipping: ${job.name}`);
103
+ continue;
104
+ }
105
+
106
+ if (fireAt <= now) {
107
+ if (opts.verbose) console.log(`[bree-plugin-one-time] already past, skipping: ${job.name} (was ${job.date})`);
108
+ continue;
109
+ }
110
+
111
+ // Re-register with Bree using native date-based scheduling
112
+ const restored = {
113
+ name: job.name,
114
+ date: new Date(job.date),
115
+ ...(job.path && { path: job.path }),
116
+ ...(job.worker && { worker: job.worker }),
117
+ ...(job.env && { env: job.env }),
118
+ };
119
+
120
+ try {
121
+ await originalAdd.call(this, restored);
122
+ stillValid.push(job);
123
+ if (opts.verbose) console.log(`[bree-plugin-one-time] restored: ${job.name} fires at ${job.date}`);
124
+ } catch (err) {
125
+ console.error(`[bree-plugin-one-time] failed to restore ${job.name}:`, err.message);
126
+ }
127
+ }
128
+
129
+ // Rewrite queue with only still-valid jobs
130
+ saveQueue(stillValid);
131
+
132
+ return result;
133
+ };
134
+
135
+ // ── Wrap Bree.prototype.start ──────────────────────────────────────────────
136
+ // After start, listen for job completion to clean up the queue file.
137
+
138
+ const originalStart = Bree.prototype.start;
139
+
140
+ Bree.prototype.start = async function () {
141
+ // `worker deleted` fires when a worker thread finishes (success or error)
142
+ this.on('worker deleted', (name) => {
143
+ const queue = loadQueue();
144
+ const wasOneTime = queue.some(j => j.name === name);
145
+ if (wasOneTime) {
146
+ removeFromQueue(name);
147
+ }
148
+ });
149
+
150
+ return originalStart.call(this);
151
+ };
152
+ };
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "bree-plugin-one-time",
3
+ "version": "1.0.0",
4
+ "description": "Persistent one-time job scheduling for Bree — jobs with a date property survive scheduler restarts",
5
+ "main": "index.js",
6
+ "types": "index.d.ts",
7
+ "files": [
8
+ "index.js",
9
+ "index.d.ts"
10
+ ],
11
+ "scripts": {
12
+ "test": "node test/index.js"
13
+ },
14
+ "keywords": [
15
+ "bree",
16
+ "bree-plugin",
17
+ "scheduler",
18
+ "one-time",
19
+ "cron",
20
+ "job-queue",
21
+ "persistent"
22
+ ],
23
+ "author": "Alex Hillman <alex@indyhall.org>",
24
+ "license": "MIT",
25
+ "repository": {
26
+ "type": "git",
27
+ "url": "git+https://github.com/alexknowshtml/bree-plugin-one-time.git"
28
+ },
29
+ "peerDependencies": {
30
+ "bree": ">=9.0.0"
31
+ }
32
+ }