@getstrata/core 0.5.90 → 0.5.91

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/CHANGELOG.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # @getstrata/core changelog
2
2
 
3
+ ## 0.5.91
4
+
5
+ - `Schedule.command()` accepts any expression `Bun.cron.parse` understands. `dueTasks()` uses the next fire time in the current minute instead of a `*/N` whitelist.
6
+ - `Factory.create()` persists `make()` through subclass `persist()` and strips a placeholder `id` of `0`. No states, sequences, or relationships.
7
+
3
8
  ## 0.5.90
4
9
 
5
10
  - **Breaking identity defaults:** `appKeyPrefix()` is `strata` and `appDisplayName()` is `Strata` when `APP_KEY_PREFIX` / `APP_NAME` are unset (were `workhub` / `WorkHub`). WorkHub pins those env vars.
package/README.md CHANGED
@@ -82,7 +82,7 @@ import type { Migration } from "@getstrata/core/database/migrations/types";
82
82
  Package name: **`@getstrata/core`** (npm org [`@getstrata`](https://www.npmjs.com/org/getstrata)).
83
83
 
84
84
  1. Add `NPM_TOKEN` to GitHub repository secrets.
85
- 2. Tag a release: `git tag v0.5.95 && git push origin v0.5.95`
85
+ 2. Tag a release: `git tag v0.5.96 && git push origin v0.5.96`
86
86
  3. [Release workflow](../../.github/workflows/release.yml) builds and runs `npm publish --access public`.
87
87
 
88
88
  Previously published as `@eyk-workhub/framework@0.1.0`, deprecated in favor of this package.
@@ -2,5 +2,8 @@ declare class Factory<TRecord extends object> {
2
2
  constructor();
3
3
  protected definition(): TRecord;
4
4
  make(overrides?: Partial<TRecord>): TRecord;
5
+ create(overrides?: Partial<TRecord>): Promise<TRecord>;
6
+ protected insertable(record: TRecord): Partial<TRecord>;
7
+ protected persist(_values: Partial<TRecord>): Promise<TRecord>;
5
8
  }
6
9
  export { Factory };
@@ -7,8 +7,10 @@ interface InProcessCronJob {
7
7
  unref(): InProcessCronJob;
8
8
  }
9
9
  declare function parseScheduleExpression(expression: string, from?: Date): Date | null;
10
+ declare function isScheduleExpressionDue(expression: string, now?: Date): boolean;
11
+ declare function assertScheduleExpression(expression: string): void;
10
12
  declare function registerInProcessScheduleRunner(run: () => void | Promise<void>, expression?: string): InProcessCronJob;
11
13
  declare function installOsScheduleRunner(workerScriptPath: string, expression?: string, title?: string): Promise<void>;
12
14
  declare function uninstallOsScheduleRunner(title?: string): Promise<void>;
13
15
  export type { InProcessCronJob };
14
- export { DEFAULT_SCHEDULE_RUN_EXPRESSION, installOsScheduleRunner, OS_CRON_JOB_TITLE, parseScheduleExpression, registerInProcessScheduleRunner, uninstallOsScheduleRunner, };
16
+ export { assertScheduleExpression, DEFAULT_SCHEDULE_RUN_EXPRESSION, installOsScheduleRunner, isScheduleExpressionDue, OS_CRON_JOB_TITLE, parseScheduleExpression, registerInProcessScheduleRunner, uninstallOsScheduleRunner, };
@@ -11,6 +11,19 @@ class Factory {
11
11
  ...overrides
12
12
  };
13
13
  }
14
+ async create(overrides = {}) {
15
+ return this.persist(this.insertable(this.make(overrides)));
16
+ }
17
+ insertable(record) {
18
+ const values = { ...record };
19
+ if (values.id === 0 || values.id === undefined || values.id === null) {
20
+ delete values.id;
21
+ }
22
+ return values;
23
+ }
24
+ persist(_values) {
25
+ throw new Error("Factory.persist() must be implemented to use create().");
26
+ }
14
27
  }
15
28
  export {
16
29
  Factory
@@ -5,6 +5,30 @@ var OS_CRON_JOB_TITLE = "getstrata-schedule-run";
5
5
  function parseScheduleExpression(expression, from = new Date) {
6
6
  return Bun.cron.parse(expression, from);
7
7
  }
8
+ function minuteWindow(now) {
9
+ const start = new Date(now);
10
+ start.setSeconds(0, 0);
11
+ return { start, end: new Date(start.getTime() + 60000) };
12
+ }
13
+ function isScheduleExpressionDue(expression, now = new Date) {
14
+ const { start, end } = minuteWindow(now);
15
+ const next = parseScheduleExpression(expression, new Date(start.getTime() - 1));
16
+ if (!next) {
17
+ return false;
18
+ }
19
+ const timestamp = next.getTime();
20
+ return timestamp >= start.getTime() && timestamp < end.getTime();
21
+ }
22
+ function assertScheduleExpression(expression) {
23
+ try {
24
+ const next = parseScheduleExpression(expression);
25
+ if (!(next instanceof Date) || Number.isNaN(next.getTime())) {
26
+ throw new Error("unparsable");
27
+ }
28
+ } catch {
29
+ throw new Error(`Unsupported schedule expression "${expression}".`);
30
+ }
31
+ }
8
32
  function registerInProcessScheduleRunner(run, expression = DEFAULT_SCHEDULE_RUN_EXPRESSION) {
9
33
  const register = Bun.cron;
10
34
  return register(expression, async () => {
@@ -20,7 +44,9 @@ async function uninstallOsScheduleRunner(title = OS_CRON_JOB_TITLE) {
20
44
  export {
21
45
  DEFAULT_SCHEDULE_RUN_EXPRESSION,
22
46
  OS_CRON_JOB_TITLE,
47
+ assertScheduleExpression,
23
48
  installOsScheduleRunner,
49
+ isScheduleExpressionDue,
24
50
  parseScheduleExpression,
25
51
  registerInProcessScheduleRunner,
26
52
  uninstallOsScheduleRunner
@@ -1,34 +1,57 @@
1
1
  // @bun
2
- // ../../src/core/scheduler/schedule.ts
3
- function isSupportedScheduleExpression(expression) {
4
- if (expression === "* * * * *") {
5
- return true;
2
+ // ../../src/core/scheduler/osCron.ts
3
+ var DEFAULT_SCHEDULE_RUN_EXPRESSION = "* * * * *";
4
+ var OS_CRON_JOB_TITLE = "getstrata-schedule-run";
5
+ function parseScheduleExpression(expression, from = new Date) {
6
+ return Bun.cron.parse(expression, from);
7
+ }
8
+ function minuteWindow(now) {
9
+ const start = new Date(now);
10
+ start.setSeconds(0, 0);
11
+ return { start, end: new Date(start.getTime() + 60000) };
12
+ }
13
+ function isScheduleExpressionDue(expression, now = new Date) {
14
+ const { start, end } = minuteWindow(now);
15
+ const next = parseScheduleExpression(expression, new Date(start.getTime() - 1));
16
+ if (!next) {
17
+ return false;
18
+ }
19
+ const timestamp = next.getTime();
20
+ return timestamp >= start.getTime() && timestamp < end.getTime();
21
+ }
22
+ function assertScheduleExpression(expression) {
23
+ try {
24
+ const next = parseScheduleExpression(expression);
25
+ if (!(next instanceof Date) || Number.isNaN(next.getTime())) {
26
+ throw new Error("unparsable");
27
+ }
28
+ } catch {
29
+ throw new Error(`Unsupported schedule expression "${expression}".`);
6
30
  }
7
- return /^(\*\/\d+)( \*){4}$/.test(expression);
31
+ }
32
+ function registerInProcessScheduleRunner(run, expression = DEFAULT_SCHEDULE_RUN_EXPRESSION) {
33
+ const register = Bun.cron;
34
+ return register(expression, async () => {
35
+ await run();
36
+ }).unref();
37
+ }
38
+ async function installOsScheduleRunner(workerScriptPath, expression = DEFAULT_SCHEDULE_RUN_EXPRESSION, title = OS_CRON_JOB_TITLE) {
39
+ await Bun.cron(workerScriptPath, expression, title);
40
+ }
41
+ async function uninstallOsScheduleRunner(title = OS_CRON_JOB_TITLE) {
42
+ await Bun.cron.remove(title);
8
43
  }
9
44
 
45
+ // ../../src/core/scheduler/schedule.ts
10
46
  class Schedule {
11
47
  tasks = [];
12
48
  command(expression, name, run) {
13
- if (!isSupportedScheduleExpression(expression)) {
14
- throw new Error(`Unsupported schedule expression "${expression}". Only "* * * * *" and "*/N * * * *" are implemented.`);
15
- }
49
+ assertScheduleExpression(expression);
16
50
  this.tasks.push({ expression, name, run });
17
51
  return this;
18
52
  }
19
53
  dueTasks(now = new Date) {
20
- const minute = now.getMinutes();
21
- return this.tasks.filter((task) => {
22
- if (task.expression === "* * * * *") {
23
- return true;
24
- }
25
- const intervalMatch = task.expression.match(/^\*\/(\d+)(?: \*){4}$/);
26
- if (intervalMatch) {
27
- const interval = Number.parseInt(intervalMatch[1] ?? "", 10);
28
- return Number.isInteger(interval) && interval > 0 && minute % interval === 0;
29
- }
30
- return false;
31
- });
54
+ return this.tasks.filter((task) => isScheduleExpressionDue(task.expression, now));
32
55
  }
33
56
  tasksList() {
34
57
  return [...this.tasks];
package/dist/index.js CHANGED
@@ -6556,36 +6556,45 @@ async function collectQueueMetrics() {
6556
6556
  failedCount
6557
6557
  };
6558
6558
  }
6559
- // ../../src/core/scheduler/schedule.ts
6560
- function isSupportedScheduleExpression(expression) {
6561
- if (expression === "* * * * *") {
6562
- return true;
6559
+ // ../../src/core/scheduler/osCron.ts
6560
+ function parseScheduleExpression(expression, from = new Date) {
6561
+ return Bun.cron.parse(expression, from);
6562
+ }
6563
+ function minuteWindow(now) {
6564
+ const start = new Date(now);
6565
+ start.setSeconds(0, 0);
6566
+ return { start, end: new Date(start.getTime() + 60000) };
6567
+ }
6568
+ function isScheduleExpressionDue(expression, now = new Date) {
6569
+ const { start, end } = minuteWindow(now);
6570
+ const next = parseScheduleExpression(expression, new Date(start.getTime() - 1));
6571
+ if (!next) {
6572
+ return false;
6573
+ }
6574
+ const timestamp = next.getTime();
6575
+ return timestamp >= start.getTime() && timestamp < end.getTime();
6576
+ }
6577
+ function assertScheduleExpression(expression) {
6578
+ try {
6579
+ const next = parseScheduleExpression(expression);
6580
+ if (!(next instanceof Date) || Number.isNaN(next.getTime())) {
6581
+ throw new Error("unparsable");
6582
+ }
6583
+ } catch {
6584
+ throw new Error(`Unsupported schedule expression "${expression}".`);
6563
6585
  }
6564
- return /^(\*\/\d+)( \*){4}$/.test(expression);
6565
6586
  }
6566
6587
 
6588
+ // ../../src/core/scheduler/schedule.ts
6567
6589
  class Schedule {
6568
6590
  tasks = [];
6569
6591
  command(expression, name, run) {
6570
- if (!isSupportedScheduleExpression(expression)) {
6571
- throw new Error(`Unsupported schedule expression "${expression}". Only "* * * * *" and "*/N * * * *" are implemented.`);
6572
- }
6592
+ assertScheduleExpression(expression);
6573
6593
  this.tasks.push({ expression, name, run });
6574
6594
  return this;
6575
6595
  }
6576
6596
  dueTasks(now = new Date) {
6577
- const minute = now.getMinutes();
6578
- return this.tasks.filter((task) => {
6579
- if (task.expression === "* * * * *") {
6580
- return true;
6581
- }
6582
- const intervalMatch = task.expression.match(/^\*\/(\d+)(?: \*){4}$/);
6583
- if (intervalMatch) {
6584
- const interval = Number.parseInt(intervalMatch[1] ?? "", 10);
6585
- return Number.isInteger(interval) && interval > 0 && minute % interval === 0;
6586
- }
6587
- return false;
6588
- });
6597
+ return this.tasks.filter((task) => isScheduleExpressionDue(task.expression, now));
6589
6598
  }
6590
6599
  tasksList() {
6591
6600
  return [...this.tasks];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getstrata/core",
3
- "version": "0.5.90",
3
+ "version": "0.5.91",
4
4
  "description": "Strata — Laravel-inspired Bun framework public API",
5
5
  "type": "module",
6
6
  "license": "MIT",