stonyx 0.2.3-alpha.7 → 0.2.3-alpha.9

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.
@@ -0,0 +1,282 @@
1
+ # Cron Conventions
2
+
3
+ Scheduling conventions for `@stonyx/cron`. Covers the legacy interval API and the advanced scheduling system.
4
+
5
+ ## Legacy API (Simple Intervals)
6
+
7
+ For basic recurring callbacks, use the `Cron` class directly:
8
+
9
+ ```js
10
+ import Cron from '@stonyx/cron';
11
+
12
+ const cron = new Cron();
13
+
14
+ // register(key, callback, intervalSeconds, runOnInit?)
15
+ cron.register('health-check', async () => {
16
+ await checkHealth();
17
+ }, 300, true);
18
+
19
+ // Unregister when done
20
+ cron.unregister('health-check');
21
+ ```
22
+
23
+ `Cron` is a singleton — only one instance per process.
24
+
25
+ ## Advanced Scheduling (CronService)
26
+
27
+ For jobs with cron expressions, one-shot scheduling, AI input normalization, and run history:
28
+
29
+ ```js
30
+ import CronService from '@stonyx/cron/service';
31
+
32
+ const service = new CronService();
33
+
34
+ service.onJobDue = async (job) => {
35
+ // Execute the job's work
36
+ return { status: 'ok', summary: 'completed' };
37
+ };
38
+
39
+ await service.start();
40
+ ```
41
+
42
+ ### Schedule Kinds
43
+
44
+ Three schedule types, specified in `schedule.kind`:
45
+
46
+ | Kind | Purpose | Required fields |
47
+ |------|---------|----------------|
48
+ | `every` | Recurring interval | `everyMs` (milliseconds) |
49
+ | `cron` | Cron expression | `expr` (5-field), optional `tz` |
50
+ | `at` | One-shot | `at` (ISO-8601 string) |
51
+
52
+ ```js
53
+ // Recurring every 60 seconds
54
+ await service.add({
55
+ name: 'Diagnostics',
56
+ schedule: { kind: 'every', everyMs: 60_000 },
57
+ payload: { kind: 'agentTurn', message: 'run diagnostics' },
58
+ });
59
+
60
+ // Daily at 9am Eastern
61
+ await service.add({
62
+ name: 'Morning Report',
63
+ schedule: { kind: 'cron', expr: '0 9 * * *', tz: 'America/New_York' },
64
+ payload: { kind: 'agentTurn', message: 'generate morning report' },
65
+ });
66
+
67
+ // One-shot reminder (auto-deletes after run)
68
+ await service.add({
69
+ name: 'Reminder',
70
+ schedule: { kind: 'at', at: '2026-07-01T12:00:00Z' },
71
+ payload: { kind: 'agentTurn', message: 'follow up on PR' },
72
+ });
73
+ ```
74
+
75
+ ### Payload Kinds
76
+
77
+ | Kind | Target | Key field |
78
+ |------|--------|-----------|
79
+ | `agentTurn` | Isolated agent session | `message` |
80
+ | `systemEvent` | Main session | `text` |
81
+
82
+ `sessionTarget` is inferred automatically: `agentTurn` → `isolated`, `systemEvent` → `main`.
83
+
84
+ ### CRUD
85
+
86
+ ```js
87
+ const job = await service.add({ ... }); // Create
88
+ const found = service.get(job.id); // Read
89
+ const updated = await service.update(job.id, { name: 'Renamed' }); // Update
90
+ await service.remove(job.id); // Delete
91
+
92
+ // List (excludes disabled by default)
93
+ const jobs = service.list();
94
+ const all = service.list({ includeDisabled: true });
95
+ ```
96
+
97
+ ### Manual Execution
98
+
99
+ ```js
100
+ // Force-run regardless of schedule
101
+ await service.run(job.id, 'force');
102
+
103
+ // Only run if due
104
+ await service.run(job.id, 'due');
105
+ ```
106
+
107
+ ### Run History
108
+
109
+ ```js
110
+ const history = service.runs(job.id);
111
+ // [{ status, error?, summary?, runAtMs, durationMs, nextRunAtMs, ts }]
112
+ ```
113
+
114
+ ### AI Input Normalization
115
+
116
+ CronService accepts loose input from AI tool calls and normalizes it:
117
+
118
+ - Missing `schedule.kind` is inferred from fields (`everyMs` → `every`, `expr` → `cron`, `at` → `at`)
119
+ - Bare `message` or `text` at the top level is wrapped into a `payload`
120
+ - `deleteAfterRun` is auto-set for one-shot (`at`) jobs
121
+ - `delivery: { mode: 'announce' }` is auto-set for isolated `agentTurn` jobs
122
+
123
+ ```js
124
+ // AI might send this flat structure
125
+ await service.add({
126
+ name: 'Weather Check',
127
+ schedule: { everyMs: 120000 },
128
+ message: 'check the weather',
129
+ });
130
+
131
+ // Normalized to:
132
+ // {
133
+ // schedule: { kind: 'every', everyMs: 120000 },
134
+ // payload: { kind: 'agentTurn', message: 'check the weather' },
135
+ // sessionTarget: 'isolated',
136
+ // delivery: { mode: 'announce' },
137
+ // }
138
+ ```
139
+
140
+ ## ORM Data Model
141
+
142
+ When persisting cron data with `@stonyx/orm`, use the following model structure.
143
+
144
+ ### Models
145
+
146
+ ```
147
+ models/
148
+ cron-job.js
149
+ cron-job/
150
+ schedule.js
151
+ payload.js
152
+ state.js
153
+ delivery.js
154
+ cron-run.js
155
+ ```
156
+
157
+ **`cron-job.js`** — parent model with `belongsTo` for schedule, payload, state, and delivery sub-models (property flattening rule — no passthrough objects):
158
+
159
+ ```js
160
+ import { Model, attr, belongsTo } from '@stonyx/orm';
161
+
162
+ export default class CronJobModel extends Model {
163
+ name = attr('string');
164
+ description = attr('string');
165
+ enabled = attr('boolean');
166
+ deleteAfterRun = attr('boolean');
167
+ sessionTarget = attr('string');
168
+ wakeMode = attr('string');
169
+ createdAtMs = attr('number');
170
+ updatedAtMs = attr('number');
171
+
172
+ schedule = belongsTo('cron-job/schedule');
173
+ payload = belongsTo('cron-job/payload');
174
+ state = belongsTo('cron-job/state');
175
+ delivery = belongsTo('cron-job/delivery');
176
+ }
177
+ ```
178
+
179
+ **`cron-job/schedule.js`**
180
+
181
+ ```js
182
+ import { Model, attr } from '@stonyx/orm';
183
+
184
+ export default class CronJobScheduleModel extends Model {
185
+ kind = attr('string');
186
+ at = attr('string');
187
+ everyMs = attr('number');
188
+ anchorMs = attr('number');
189
+ expr = attr('string');
190
+ tz = attr('string');
191
+ }
192
+ ```
193
+
194
+ **`cron-job/payload.js`**
195
+
196
+ ```js
197
+ import { Model, attr } from '@stonyx/orm';
198
+
199
+ export default class CronJobPayloadModel extends Model {
200
+ kind = attr('string');
201
+ message = attr('string');
202
+ text = attr('string');
203
+ }
204
+ ```
205
+
206
+ **`cron-job/state.js`**
207
+
208
+ ```js
209
+ import { Model, attr } from '@stonyx/orm';
210
+
211
+ export default class CronJobStateModel extends Model {
212
+ nextRunAtMs = attr('number');
213
+ runningAtMs = attr('number');
214
+ lastRunAtMs = attr('number');
215
+ lastStatus = attr('string');
216
+ lastError = attr('string');
217
+ lastDurationMs = attr('number');
218
+ consecutiveErrors = attr('number');
219
+ scheduleErrorCount = attr('number');
220
+ }
221
+ ```
222
+
223
+ **`cron-job/delivery.js`**
224
+
225
+ ```js
226
+ import { Model, attr } from '@stonyx/orm';
227
+
228
+ export default class CronJobDeliveryModel extends Model {
229
+ mode = attr('string');
230
+ }
231
+ ```
232
+
233
+ **`cron-run.js`**
234
+
235
+ ```js
236
+ import { Model, attr } from '@stonyx/orm';
237
+
238
+ export default class CronRunModel extends Model {
239
+ jobId = attr('string');
240
+ status = attr('string');
241
+ error = attr('string');
242
+ summary = attr('string');
243
+ runAtMs = attr('number');
244
+ durationMs = attr('number');
245
+ nextRunAtMs = attr('number');
246
+ ts = attr('number');
247
+ }
248
+ ```
249
+
250
+ **Property ordering:** `attr()` → `belongsTo()` (on parent model).
251
+
252
+ ### DB Schema
253
+
254
+ ```js
255
+ import { Model, hasMany } from '@stonyx/orm';
256
+
257
+ export default class DBModel extends Model {
258
+ cronJobs = hasMany('cron-job');
259
+ cronJobSchedules = hasMany('cron-job/schedule');
260
+ cronJobPayloads = hasMany('cron-job/payload');
261
+ cronJobStates = hasMany('cron-job/state');
262
+ cronJobDeliveries = hasMany('cron-job/delivery');
263
+ cronRuns = hasMany('cron-run');
264
+ }
265
+ ```
266
+
267
+ ## Configuration
268
+
269
+ ```js
270
+ // config/environment.js
271
+ export default {
272
+ cron: {
273
+ log: true, // enable cron logging (uses stonyx/log)
274
+ },
275
+ }
276
+ ```
277
+
278
+ ## When to Use Which
279
+
280
+ - **Simple recurring callback** → `Cron.register(key, callback, interval)`
281
+ - **Scheduled jobs with history, CRUD, AI input** → `CronService`
282
+ - **Never use raw `setInterval` or `setTimeout`** for recurring work
@@ -86,9 +86,22 @@ All pub/sub event handling. Never create custom event emitters.
86
86
 
87
87
  All scheduled and interval tasks. Never use raw `setInterval` or `setTimeout` for recurring work.
88
88
 
89
+ **Legacy API** — simple recurring callbacks:
90
+
89
91
  - `register(key, callback, interval, runOnInit?)` — schedule a recurring job
90
92
  - `unregister(key)` — cancel a job
91
93
 
94
+ **Advanced API** (`@stonyx/cron/service`) — full scheduling with CRUD, run history, and AI normalization:
95
+
96
+ - `add(input)` / `get(id)` / `update(id, patch)` / `remove(id)` / `list(opts?)` — CRUD
97
+ - `run(id, mode?)` — manual execution (`'force'` or `'due'`)
98
+ - `runs(id, limit?)` — run history
99
+ - `onJobDue` — callback for job execution
100
+
101
+ Three schedule kinds: `every` (interval), `cron` (expression), `at` (one-shot).
102
+
103
+ See [Cron Conventions](./cron-conventions.md) for full details.
104
+
92
105
  Configurable via `config/environment.js`:
93
106
 
94
107
  ```js
@@ -18,6 +18,7 @@ Universal rules that apply to every Stonyx project. Section-specific conventions
18
18
 
19
19
  - [Project Structure](./project-structure.md) — directory layout, file organization, config conventions
20
20
  - [Framework Modules](./framework-modules.md) — when to use which `@stonyx/*` module
21
+ - [Cron Conventions](./cron-conventions.md) — scheduling, job model, CronService API, ORM data model
21
22
  - [ORM Conventions](./orm-conventions.md) — models, serializers, access control, transforms, hooks
22
23
  - [REST Conventions](./rest-conventions.md) — REST server request classes and handlers
23
24
  - [Discord Conventions](./discord-conventions.md) — Discord bot commands and event handlers
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "stonyx",
3
- "version": "0.2.3-alpha.7",
3
+ "version": "0.2.3-alpha.9",
4
4
  "description": "Base stonyx framework module",
5
5
  "main": "main.js",
6
6
  "type": "module",