@portel/photon-core 2.11.0 → 2.12.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.
@@ -0,0 +1,283 @@
1
+ /**
2
+ * Runtime Scheduling System
3
+ *
4
+ * Programmatic scheduling for photons — create, pause, resume, and cancel
5
+ * scheduled tasks at runtime. Available as `this.schedule` on Photon.
6
+ *
7
+ * Complements static `@scheduled`/`@cron` JSDoc tags with dynamic scheduling.
8
+ * Schedules persist to disk; the daemon reads and executes them.
9
+ *
10
+ * Storage: ~/.photon/schedules/{photonId}/{taskId}.json
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * export default class Cleanup extends Photon {
15
+ * async setup() {
16
+ * await this.schedule.create({
17
+ * name: 'nightly-cleanup',
18
+ * schedule: '0 0 * * *',
19
+ * method: 'purge',
20
+ * params: { olderThan: 30 },
21
+ * });
22
+ * }
23
+ *
24
+ * async purge({ olderThan }: { olderThan: number }) {
25
+ * // ... cleanup logic
26
+ * }
27
+ * }
28
+ * ```
29
+ */
30
+ import * as fs from 'fs/promises';
31
+ import * as path from 'path';
32
+ import * as os from 'os';
33
+ import { randomUUID } from 'crypto';
34
+ // ── Cron Shorthands ────────────────────────────────────────────────────
35
+ const CRON_SHORTHANDS = {
36
+ '@yearly': '0 0 1 1 *',
37
+ '@annually': '0 0 1 1 *',
38
+ '@monthly': '0 0 1 * *',
39
+ '@weekly': '0 0 * * 0',
40
+ '@daily': '0 0 * * *',
41
+ '@midnight': '0 0 * * *',
42
+ '@hourly': '0 * * * *',
43
+ };
44
+ /**
45
+ * Resolve cron shorthands and validate basic format.
46
+ * Returns the resolved 5-field cron expression.
47
+ */
48
+ function resolveCron(schedule) {
49
+ const trimmed = schedule.trim();
50
+ // Check shorthands
51
+ const shorthand = CRON_SHORTHANDS[trimmed.toLowerCase()];
52
+ if (shorthand)
53
+ return shorthand;
54
+ // Validate 5-field cron format
55
+ const fields = trimmed.split(/\s+/);
56
+ if (fields.length !== 5) {
57
+ throw new Error(`Invalid cron expression: '${schedule}'. Expected 5 fields (minute hour day month weekday) or a shorthand (@hourly, @daily, @weekly, @monthly, @yearly).`);
58
+ }
59
+ return trimmed;
60
+ }
61
+ // ── Storage Helpers ────────────────────────────────────────────────────
62
+ function getSchedulesDir() {
63
+ return process.env.PHOTON_SCHEDULES_DIR || path.join(os.homedir(), '.photon', 'schedules');
64
+ }
65
+ function photonScheduleDir(photonId) {
66
+ const safeName = photonId.replace(/[^a-zA-Z0-9_-]/g, '_');
67
+ return path.join(getSchedulesDir(), safeName);
68
+ }
69
+ function taskPath(photonId, taskId) {
70
+ return path.join(photonScheduleDir(photonId), `${taskId}.json`);
71
+ }
72
+ async function ensureDir(dir) {
73
+ try {
74
+ await fs.mkdir(dir, { recursive: true });
75
+ }
76
+ catch (err) {
77
+ if (err.code !== 'EEXIST')
78
+ throw err;
79
+ }
80
+ }
81
+ // ── Schedule Provider ──────────────────────────────────────────────────
82
+ /**
83
+ * Runtime Schedule Provider
84
+ *
85
+ * Provides CRUD operations for scheduled tasks.
86
+ * Tasks are persisted as JSON files that the daemon watches and executes.
87
+ */
88
+ export class ScheduleProvider {
89
+ _photonId;
90
+ constructor(photonId) {
91
+ this._photonId = photonId;
92
+ }
93
+ /**
94
+ * Create a new scheduled task
95
+ *
96
+ * @example
97
+ * ```typescript
98
+ * await this.schedule.create({
99
+ * name: 'daily-report',
100
+ * schedule: '0 9 * * *',
101
+ * method: 'generate',
102
+ * params: { format: 'pdf' },
103
+ * });
104
+ * ```
105
+ */
106
+ async create(options) {
107
+ const cron = resolveCron(options.schedule);
108
+ // Check for duplicate name
109
+ const existing = await this.getByName(options.name);
110
+ if (existing) {
111
+ throw new Error(`Schedule '${options.name}' already exists (id: ${existing.id}). Use update() to modify it.`);
112
+ }
113
+ const task = {
114
+ id: randomUUID(),
115
+ name: options.name,
116
+ description: options.description,
117
+ cron,
118
+ method: options.method,
119
+ params: options.params || {},
120
+ fireOnce: options.fireOnce ?? false,
121
+ maxExecutions: options.maxExecutions ?? 0,
122
+ status: 'active',
123
+ createdAt: new Date().toISOString(),
124
+ executionCount: 0,
125
+ photonId: this._photonId,
126
+ };
127
+ await this._save(task);
128
+ return task;
129
+ }
130
+ /**
131
+ * Get a scheduled task by ID
132
+ */
133
+ async get(taskId) {
134
+ try {
135
+ const content = await fs.readFile(taskPath(this._photonId, taskId), 'utf-8');
136
+ return JSON.parse(content);
137
+ }
138
+ catch (err) {
139
+ if (err.code === 'ENOENT')
140
+ return null;
141
+ throw err;
142
+ }
143
+ }
144
+ /**
145
+ * Get a scheduled task by name
146
+ */
147
+ async getByName(name) {
148
+ const tasks = await this.list();
149
+ return tasks.find(t => t.name === name) || null;
150
+ }
151
+ /**
152
+ * List all scheduled tasks, optionally filtered by status
153
+ */
154
+ async list(status) {
155
+ const dir = photonScheduleDir(this._photonId);
156
+ let files;
157
+ try {
158
+ files = await fs.readdir(dir);
159
+ }
160
+ catch (err) {
161
+ if (err.code === 'ENOENT')
162
+ return [];
163
+ throw err;
164
+ }
165
+ const tasks = [];
166
+ for (const file of files) {
167
+ if (!file.endsWith('.json'))
168
+ continue;
169
+ try {
170
+ const content = await fs.readFile(path.join(dir, file), 'utf-8');
171
+ const task = JSON.parse(content);
172
+ if (!status || task.status === status) {
173
+ tasks.push(task);
174
+ }
175
+ }
176
+ catch {
177
+ // Skip corrupt files
178
+ }
179
+ }
180
+ return tasks.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
181
+ }
182
+ /**
183
+ * Update an existing scheduled task
184
+ */
185
+ async update(taskId, updates) {
186
+ const task = await this.get(taskId);
187
+ if (!task) {
188
+ throw new Error(`Schedule not found: ${taskId}`);
189
+ }
190
+ if (updates.schedule !== undefined) {
191
+ task.cron = resolveCron(updates.schedule);
192
+ }
193
+ if (updates.method !== undefined)
194
+ task.method = updates.method;
195
+ if (updates.params !== undefined)
196
+ task.params = updates.params;
197
+ if (updates.description !== undefined)
198
+ task.description = updates.description;
199
+ if (updates.fireOnce !== undefined)
200
+ task.fireOnce = updates.fireOnce;
201
+ if (updates.maxExecutions !== undefined)
202
+ task.maxExecutions = updates.maxExecutions;
203
+ await this._save(task);
204
+ return task;
205
+ }
206
+ /**
207
+ * Pause a scheduled task (stops execution until resumed)
208
+ */
209
+ async pause(taskId) {
210
+ const task = await this.get(taskId);
211
+ if (!task)
212
+ throw new Error(`Schedule not found: ${taskId}`);
213
+ if (task.status !== 'active') {
214
+ throw new Error(`Cannot pause task with status '${task.status}'. Only active tasks can be paused.`);
215
+ }
216
+ task.status = 'paused';
217
+ await this._save(task);
218
+ return task;
219
+ }
220
+ /**
221
+ * Resume a paused scheduled task
222
+ */
223
+ async resume(taskId) {
224
+ const task = await this.get(taskId);
225
+ if (!task)
226
+ throw new Error(`Schedule not found: ${taskId}`);
227
+ if (task.status !== 'paused') {
228
+ throw new Error(`Cannot resume task with status '${task.status}'. Only paused tasks can be resumed.`);
229
+ }
230
+ task.status = 'active';
231
+ await this._save(task);
232
+ return task;
233
+ }
234
+ /**
235
+ * Cancel (delete) a scheduled task
236
+ */
237
+ async cancel(taskId) {
238
+ try {
239
+ await fs.unlink(taskPath(this._photonId, taskId));
240
+ return true;
241
+ }
242
+ catch (err) {
243
+ if (err.code === 'ENOENT')
244
+ return false;
245
+ throw err;
246
+ }
247
+ }
248
+ /**
249
+ * Cancel a scheduled task by name
250
+ */
251
+ async cancelByName(name) {
252
+ const task = await this.getByName(name);
253
+ if (!task)
254
+ return false;
255
+ return this.cancel(task.id);
256
+ }
257
+ /**
258
+ * Check if a schedule with the given name exists
259
+ */
260
+ async has(name) {
261
+ const task = await this.getByName(name);
262
+ return task !== null;
263
+ }
264
+ /**
265
+ * Cancel all scheduled tasks for this photon
266
+ */
267
+ async cancelAll() {
268
+ const tasks = await this.list();
269
+ let count = 0;
270
+ for (const task of tasks) {
271
+ if (await this.cancel(task.id))
272
+ count++;
273
+ }
274
+ return count;
275
+ }
276
+ /** @internal */
277
+ async _save(task) {
278
+ const dir = photonScheduleDir(this._photonId);
279
+ await ensureDir(dir);
280
+ await fs.writeFile(taskPath(this._photonId, task.id), JSON.stringify(task, null, 2));
281
+ }
282
+ }
283
+ //# sourceMappingURL=schedule.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"schedule.js","sourceRoot":"","sources":["../src/schedule.ts"],"names":[],"mappings":"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"}
@@ -57,6 +57,11 @@ export declare class SchemaExtractor {
57
57
  * Extracts: type, optional, readonly
58
58
  */
59
59
  private buildSchemaFromType;
60
+ /**
61
+ * Extract JSDoc description from a property signature.
62
+ * Handles both /** comment * / style and // comment style.
63
+ */
64
+ private getPropertyJsDoc;
60
65
  /**
61
66
  * Convert TypeScript type node to JSON schema
62
67
  */
@@ -200,13 +205,12 @@ export declare class SchemaExtractor {
200
205
  */
201
206
  private extractContentPriority;
202
207
  /**
203
- * Extract structured output schema from @returns.field {type} tags
204
- * @returns.id {string} Task ID
205
- * @returns.title {string} Task title
206
- * @returns.done {boolean} Completion status
207
- * → { type: 'object', properties: { id: { type: 'string', description: 'Task ID' }, ... } }
208
+ * Infer output schema from TypeScript return type annotation.
209
+ * Unwraps Promise<T> and converts T to JSON Schema.
210
+ * Property descriptions come from JSDoc on the type/interface properties.
211
+ * Only produces a schema for object return types (not primitives/arrays).
208
212
  */
209
- private extractOutputSchema;
213
+ private inferOutputSchemaFromReturnType;
210
214
  /**
211
215
  * Extract notification subscriptions from @notify-on tag
212
216
  * Specifies which event types this photon is interested in
@@ -1 +1 @@
1
- {"version":3,"file":"schema-extractor.d.ts","sourceRoot":"","sources":["../src/schema-extractor.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schema-extractor.d.ts","sourceRoot":"","sources":["../src/schema-extractor.ts"],"names":[],"mappings":"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"}
@@ -204,7 +204,7 @@ export class SchemaExtractor {
204
204
  const openWorldHint = this.extractOpenWorldHint(jsdoc);
205
205
  const audience = this.extractAudience(jsdoc);
206
206
  const contentPriority = this.extractContentPriority(jsdoc);
207
- const outputSchema = this.extractOutputSchema(jsdoc);
207
+ const outputSchema = this.inferOutputSchemaFromReturnType(member, sourceFile);
208
208
  // Daemon features
209
209
  const webhook = this.extractWebhook(jsdoc, methodName);
210
210
  const scheduled = this.extractScheduled(jsdoc, methodName);
@@ -563,6 +563,11 @@ export class SchemaExtractor {
563
563
  else {
564
564
  properties[propName] = { type: 'object' };
565
565
  }
566
+ // Extract JSDoc description from property (e.g., /** Task ID */ id: string)
567
+ const jsDocComment = this.getPropertyJsDoc(member, sourceFile);
568
+ if (jsDocComment) {
569
+ properties[propName].description = jsDocComment;
570
+ }
566
571
  // Add readonly from TypeScript (JSDoc can override)
567
572
  if (isReadonly) {
568
573
  properties[propName]._tsReadOnly = true;
@@ -572,6 +577,29 @@ export class SchemaExtractor {
572
577
  }
573
578
  return { properties, required };
574
579
  }
580
+ /**
581
+ * Extract JSDoc description from a property signature.
582
+ * Handles both /** comment * / style and // comment style.
583
+ */
584
+ getPropertyJsDoc(member, sourceFile) {
585
+ // Check for JSDoc comments attached to the node
586
+ const jsDocNodes = member.jsDoc;
587
+ if (jsDocNodes && jsDocNodes.length > 0) {
588
+ const comment = jsDocNodes[0].comment;
589
+ if (typeof comment === 'string')
590
+ return comment.trim();
591
+ }
592
+ // Fallback: look for leading comment in source text
593
+ const fullText = sourceFile.getFullText();
594
+ const start = member.getFullStart();
595
+ const leading = fullText.substring(start, member.getStart(sourceFile)).trim();
596
+ // Match /** ... */ or /* ... */
597
+ const blockMatch = leading.match(/\/\*\*?\s*([\s\S]*?)\s*\*\//);
598
+ if (blockMatch) {
599
+ return blockMatch[1].replace(/\s*\*\s*/g, ' ').trim();
600
+ }
601
+ return undefined;
602
+ }
575
603
  /**
576
604
  * Convert TypeScript type node to JSON schema
577
605
  */
@@ -685,6 +713,12 @@ export class SchemaExtractor {
685
713
  resolved = node.type;
686
714
  return;
687
715
  }
716
+ // Also resolve interface declarations → synthesize a TypeLiteral
717
+ if (ts.isInterfaceDeclaration(node) && node.name.text === typeName) {
718
+ // Create a synthetic TypeLiteralNode from interface members
719
+ resolved = ts.factory.createTypeLiteralNode(node.members.filter(ts.isPropertySignature));
720
+ return;
721
+ }
688
722
  ts.forEachChild(node, visit);
689
723
  };
690
724
  visit(sourceFile);
@@ -1626,32 +1660,36 @@ export class SchemaExtractor {
1626
1660
  return undefined;
1627
1661
  }
1628
1662
  /**
1629
- * Extract structured output schema from @returns.field {type} tags
1630
- * @returns.id {string} Task ID
1631
- * @returns.title {string} Task title
1632
- * @returns.done {boolean} Completion status
1633
- * → { type: 'object', properties: { id: { type: 'string', description: 'Task ID' }, ... } }
1663
+ * Infer output schema from TypeScript return type annotation.
1664
+ * Unwraps Promise<T> and converts T to JSON Schema.
1665
+ * Property descriptions come from JSDoc on the type/interface properties.
1666
+ * Only produces a schema for object return types (not primitives/arrays).
1634
1667
  */
1635
- extractOutputSchema(jsdocContent) {
1636
- const fieldRegex = /@returns\.(\w+)\s+\{(\w+)\}\s*([^\n*]*)/g;
1637
- const properties = {};
1638
- let match;
1639
- while ((match = fieldRegex.exec(jsdocContent)) !== null) {
1640
- const [, field, type, desc] = match;
1641
- const jsonType = type.toLowerCase() === 'boolean' ? 'boolean'
1642
- : type.toLowerCase() === 'number' || type.toLowerCase() === 'integer' ? 'number'
1643
- : type.toLowerCase() === 'array' ? 'array'
1644
- : type.toLowerCase() === 'object' ? 'object'
1645
- : 'string';
1646
- properties[field] = { type: jsonType };
1647
- const trimmedDesc = desc.replace(/\s*\*\s*/g, ' ').trim();
1648
- if (trimmedDesc) {
1649
- properties[field].description = trimmedDesc;
1650
- }
1651
- }
1652
- if (Object.keys(properties).length === 0)
1668
+ inferOutputSchemaFromReturnType(method, sourceFile) {
1669
+ const returnType = method.type;
1670
+ if (!returnType)
1653
1671
  return undefined;
1654
- return { type: 'object', properties };
1672
+ // Unwrap Promise<T> T
1673
+ let innerType = returnType;
1674
+ if (ts.isTypeReferenceNode(returnType)) {
1675
+ const typeName = returnType.typeName.getText(sourceFile);
1676
+ if (typeName === 'Promise' && returnType.typeArguments?.length) {
1677
+ innerType = returnType.typeArguments[0];
1678
+ }
1679
+ }
1680
+ // Convert to JSON Schema
1681
+ const schema = this.typeNodeToSchema(innerType, sourceFile);
1682
+ // Only produce outputSchema for object types with properties
1683
+ if (schema.type === 'object' && schema.properties && Object.keys(schema.properties).length > 0) {
1684
+ const result = {
1685
+ type: 'object',
1686
+ properties: schema.properties,
1687
+ };
1688
+ if (schema.required?.length)
1689
+ result.required = schema.required;
1690
+ return result;
1691
+ }
1692
+ return undefined;
1655
1693
  }
1656
1694
  /**
1657
1695
  * Extract notification subscriptions from @notify-on tag