@getstrata/core 0.5.90 → 0.5.97

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,18 @@
1
1
  # @getstrata/core changelog
2
2
 
3
+ ## 0.5.97
4
+
5
+ - `mapDatabaseError()` recognizes HTTP errors by `status` + `message`, not only `instanceof HttpError`. Subpath builds duplicate the class, so a thrown `ForbiddenError` was remapped to `400 Bad Request`.
6
+
7
+ ## 0.5.92
8
+
9
+ - Re-export `resolveMembershipLookup` and `runWithMembershipContext` from the public barrel. Shared subpath shims re-export that barrel, so published `@getstrata/bootstrap` can import `@getstrata/core/auth/membershipContext` without a missing-export boot failure.
10
+
11
+ ## 0.5.91
12
+
13
+ - `Schedule.command()` accepts any expression `Bun.cron.parse` understands. `dueTasks()` uses the next fire time in the current minute instead of a `*/N` whitelist.
14
+ - `Factory.create()` persists `make()` through subclass `persist()` and strips a placeholder `id` of `0`. No states, sequences, or relationships.
15
+
3
16
  ## 0.5.90
4
17
 
5
18
  - **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.
@@ -1,4 +1,4 @@
1
- import { HttpError } from "@getstrata/core/errors/http";
1
+ import { type HttpError } from "@getstrata/core/errors/http";
2
2
  interface PostgresErrorLike {
3
3
  code?: string;
4
4
  errno?: string | number;
@@ -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 };
@@ -30,4 +30,11 @@ declare class PayloadTooLargeError extends HttpError {
30
30
  declare class PreconditionFailedError extends HttpError {
31
31
  constructor(message?: string, details?: unknown);
32
32
  }
33
- export { BadRequestError, ConflictError, ForbiddenError, HttpError, NotFoundError, PayloadTooLargeError, PreconditionFailedError, UnauthorizedError, UnprocessableEntityError, ValidationError, };
33
+ interface HttpErrorLike {
34
+ status: number;
35
+ message: string;
36
+ details?: unknown;
37
+ }
38
+ declare function isHttpErrorLike(error: unknown): error is HttpErrorLike;
39
+ declare function toHttpError(error: unknown): HttpError | null;
40
+ export { BadRequestError, ConflictError, ForbiddenError, HttpError, isHttpErrorLike, NotFoundError, PayloadTooLargeError, PreconditionFailedError, toHttpError, UnauthorizedError, UnprocessableEntityError, ValidationError, };
@@ -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, };
@@ -3,7 +3,7 @@
3
3
  import {
4
4
  BadRequestError,
5
5
  ConflictError,
6
- HttpError,
6
+ toHttpError,
7
7
  UnprocessableEntityError
8
8
  } from "@getstrata/core/errors/http";
9
9
  function isPostgresError(error) {
@@ -22,8 +22,9 @@ function getPostgresSqlState(error) {
22
22
  return;
23
23
  }
24
24
  function mapDatabaseError(error) {
25
- if (error instanceof HttpError) {
26
- return error;
25
+ const httpError = toHttpError(error);
26
+ if (httpError) {
27
+ return httpError;
27
28
  }
28
29
  if (!isPostgresError(error)) {
29
30
  const message = error instanceof Error ? error.message : "Database operation failed.";
@@ -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
@@ -1,12 +1,12 @@
1
1
  // @bun
2
2
  // ../../src/core/http/response.ts
3
- import { HttpError as HttpError3 } from "@getstrata/core/errors/http";
3
+ import { toHttpError as toHttpError3 } from "@getstrata/core/errors/http";
4
4
 
5
5
  // ../../src/core/database/errors.ts
6
6
  import {
7
7
  BadRequestError,
8
8
  ConflictError,
9
- HttpError,
9
+ toHttpError,
10
10
  UnprocessableEntityError
11
11
  } from "@getstrata/core/errors/http";
12
12
  function isPostgresError(error) {
@@ -25,8 +25,9 @@ function getPostgresSqlState(error) {
25
25
  return;
26
26
  }
27
27
  function mapDatabaseError(error) {
28
- if (error instanceof HttpError) {
29
- return error;
28
+ const httpError = toHttpError(error);
29
+ if (httpError) {
30
+ return httpError;
30
31
  }
31
32
  if (!isPostgresError(error)) {
32
33
  const message = error instanceof Error ? error.message : "Database operation failed.";
@@ -66,7 +67,7 @@ async function withDatabaseErrorHandling(operation) {
66
67
  }
67
68
 
68
69
  // ../../src/core/http/webErrorResponse.ts
69
- import { HttpError as HttpError2, UnauthorizedError, ValidationError } from "@getstrata/core/errors/http";
70
+ import { toHttpError as toHttpError2, ValidationError } from "@getstrata/core/errors/http";
70
71
 
71
72
  // ../../src/core/runtime/frontendMode.ts
72
73
  function readFrontendMode() {
@@ -301,11 +302,11 @@ async function webErrorResponse(error, request) {
301
302
  if (!request || !isViewsEnabled() || requestPrefersJson(request)) {
302
303
  return null;
303
304
  }
304
- const mappedError = error instanceof HttpError2 ? error : mapDatabaseError(error);
305
- if (mappedError instanceof UnauthorizedError) {
305
+ const mappedError = toHttpError2(error) ?? mapDatabaseError(error);
306
+ if (mappedError.status === 401) {
306
307
  return Response.redirect(loginRedirectLocation(request), 302);
307
308
  }
308
- const errors = mappedError instanceof ValidationError ? normalizeFieldErrors(mappedError.details) : undefined;
309
+ const errors = mappedError instanceof ValidationError || mappedError.name === "ValidationError" ? normalizeFieldErrors(mappedError.details) : undefined;
309
310
  return htmlErrorResponse({
310
311
  status: mappedError.status,
311
312
  title: errorPageTitle(mappedError.status, mappedError.message),
@@ -329,7 +330,7 @@ function noContentResponse() {
329
330
  return new Response(null, { status: 204 });
330
331
  }
331
332
  function errorResponse(error) {
332
- const mappedError = error instanceof HttpError3 ? error : mapDatabaseError(error);
333
+ const mappedError = toHttpError3(error) ?? mapDatabaseError(error);
333
334
  return Response.json({
334
335
  error: mappedError.message,
335
336
  ...mappedError.details === undefined ? {} : { details: mappedError.details }
@@ -1,12 +1,12 @@
1
1
  // @bun
2
2
  // ../../src/core/http/webErrorResponse.ts
3
- import { HttpError as HttpError2, UnauthorizedError, ValidationError } from "@getstrata/core/errors/http";
3
+ import { toHttpError as toHttpError2, ValidationError } from "@getstrata/core/errors/http";
4
4
 
5
5
  // ../../src/core/database/errors.ts
6
6
  import {
7
7
  BadRequestError,
8
8
  ConflictError,
9
- HttpError,
9
+ toHttpError,
10
10
  UnprocessableEntityError
11
11
  } from "@getstrata/core/errors/http";
12
12
  function isPostgresError(error) {
@@ -25,8 +25,9 @@ function getPostgresSqlState(error) {
25
25
  return;
26
26
  }
27
27
  function mapDatabaseError(error) {
28
- if (error instanceof HttpError) {
29
- return error;
28
+ const httpError = toHttpError(error);
29
+ if (httpError) {
30
+ return httpError;
30
31
  }
31
32
  if (!isPostgresError(error)) {
32
33
  const message = error instanceof Error ? error.message : "Database operation failed.";
@@ -298,11 +299,11 @@ async function webErrorResponse(error, request) {
298
299
  if (!request || !isViewsEnabled() || requestPrefersJson(request)) {
299
300
  return null;
300
301
  }
301
- const mappedError = error instanceof HttpError2 ? error : mapDatabaseError(error);
302
- if (mappedError instanceof UnauthorizedError) {
302
+ const mappedError = toHttpError2(error) ?? mapDatabaseError(error);
303
+ if (mappedError.status === 401) {
303
304
  return Response.redirect(loginRedirectLocation(request), 302);
304
305
  }
305
- const errors = mappedError instanceof ValidationError ? normalizeFieldErrors(mappedError.details) : undefined;
306
+ const errors = mappedError instanceof ValidationError || mappedError.name === "ValidationError" ? normalizeFieldErrors(mappedError.details) : undefined;
306
307
  return htmlErrorResponse({
307
308
  status: mappedError.status,
308
309
  title: errorPageTitle(mappedError.status, mappedError.message),
@@ -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];
@@ -10,7 +10,7 @@ export type { AuthUser } from "../core/auth/authContext.ts";
10
10
  export { authContext, currentAuthUser, runWithAuthUser } from "../core/auth/authContext.ts";
11
11
  export type { AuthGuard } from "../core/auth/guard.ts";
12
12
  export { ApiTokenGuard, AuthManager, CompositeGuard, DatabaseTokenGuard, GuestGuard, } from "../core/auth/guard.ts";
13
- export { configureMembershipLookup, currentOrganizationIds, currentOrgRole, hasMinimumOrgRole, hasOrgMembership, } from "../core/auth/membershipContext.ts";
13
+ export { configureMembershipLookup, currentOrganizationIds, currentOrgRole, hasMinimumOrgRole, hasOrgMembership, resolveMembershipLookup, runWithMembershipContext, } from "../core/auth/membershipContext.ts";
14
14
  export { createMembershipMiddleware } from "../core/auth/membershipMiddleware.ts";
15
15
  export { appendOrganizationScope, appendProjectScope, assertOrganizationReadable, assertResourceInCurrentTenant, emptyPaginateResult, resolveOrganizationScope, scopedOrganizationIds, } from "../core/auth/membershipScope.ts";
16
16
  export { default as MembershipService, resolveMembershipService, } from "../core/auth/membershipService.ts";
@@ -48,7 +48,7 @@ export { runInTransaction } from "../core/database/transaction.ts";
48
48
  export type { QueryJoin, QueryJoinOn, QueryOptions, QueryOrder, QuerySelectItem, QueryWhere, } from "../core/database/types.ts";
49
49
  export type { WhereNode } from "../core/database/whereBuilder.ts";
50
50
  export { WhereBuilder } from "../core/database/whereBuilder.ts";
51
- export { BadRequestError, ConflictError, ForbiddenError, HttpError, NotFoundError, PayloadTooLargeError, PreconditionFailedError, UnauthorizedError, UnprocessableEntityError, ValidationError, } from "../core/errors/http.ts";
51
+ export { BadRequestError, ConflictError, ForbiddenError, HttpError, isHttpErrorLike, NotFoundError, PayloadTooLargeError, PreconditionFailedError, toHttpError, UnauthorizedError, UnprocessableEntityError, ValidationError, } from "../core/errors/http.ts";
52
52
  export type { EventListener } from "../core/events/eventBus.ts";
53
53
  export { EventBus, eventBus } from "../core/events/eventBus.ts";
54
54
  export { modelEventName } from "../core/events/index.ts";
package/dist/index.js CHANGED
@@ -120,6 +120,22 @@ class PreconditionFailedError extends HttpError {
120
120
  super(412, message, details);
121
121
  }
122
122
  }
123
+ function isHttpErrorLike(error) {
124
+ if (typeof error !== "object" || error === null) {
125
+ return false;
126
+ }
127
+ const candidate = error;
128
+ return typeof candidate.status === "number" && Number.isInteger(candidate.status) && candidate.status >= 400 && candidate.status < 600 && typeof candidate.message === "string";
129
+ }
130
+ function toHttpError(error) {
131
+ if (error instanceof HttpError) {
132
+ return error;
133
+ }
134
+ if (!isHttpErrorLike(error)) {
135
+ return null;
136
+ }
137
+ return new HttpError(error.status, error.message, error.details);
138
+ }
123
139
 
124
140
  // ../../src/core/runtime/asyncContextStore.ts
125
141
  import { AsyncLocalStorage } from "async_hooks";
@@ -1575,8 +1591,9 @@ function getPostgresSqlState(error) {
1575
1591
  return;
1576
1592
  }
1577
1593
  function mapDatabaseError(error) {
1578
- if (error instanceof HttpError) {
1579
- return error;
1594
+ const httpError = toHttpError(error);
1595
+ if (httpError) {
1596
+ return httpError;
1580
1597
  }
1581
1598
  if (!isPostgresError(error)) {
1582
1599
  const message = error instanceof Error ? error.message : "Database operation failed.";
@@ -5298,11 +5315,11 @@ async function webErrorResponse(error, request) {
5298
5315
  if (!request || !isViewsEnabled() || requestPrefersJson(request)) {
5299
5316
  return null;
5300
5317
  }
5301
- const mappedError = error instanceof HttpError ? error : mapDatabaseError(error);
5302
- if (mappedError instanceof UnauthorizedError) {
5318
+ const mappedError = toHttpError(error) ?? mapDatabaseError(error);
5319
+ if (mappedError.status === 401) {
5303
5320
  return Response.redirect(loginRedirectLocation(request), 302);
5304
5321
  }
5305
- const errors = mappedError instanceof ValidationError ? normalizeFieldErrors(mappedError.details) : undefined;
5322
+ const errors = mappedError instanceof ValidationError || mappedError.name === "ValidationError" ? normalizeFieldErrors(mappedError.details) : undefined;
5306
5323
  return htmlErrorResponse({
5307
5324
  status: mappedError.status,
5308
5325
  title: errorPageTitle(mappedError.status, mappedError.message),
@@ -5326,7 +5343,7 @@ function noContentResponse() {
5326
5343
  return new Response(null, { status: 204 });
5327
5344
  }
5328
5345
  function errorResponse(error) {
5329
- const mappedError = error instanceof HttpError ? error : mapDatabaseError(error);
5346
+ const mappedError = toHttpError(error) ?? mapDatabaseError(error);
5330
5347
  return Response.json({
5331
5348
  error: mappedError.message,
5332
5349
  ...mappedError.details === undefined ? {} : { details: mappedError.details }
@@ -6556,36 +6573,45 @@ async function collectQueueMetrics() {
6556
6573
  failedCount
6557
6574
  };
6558
6575
  }
6559
- // ../../src/core/scheduler/schedule.ts
6560
- function isSupportedScheduleExpression(expression) {
6561
- if (expression === "* * * * *") {
6562
- return true;
6576
+ // ../../src/core/scheduler/osCron.ts
6577
+ function parseScheduleExpression(expression, from = new Date) {
6578
+ return Bun.cron.parse(expression, from);
6579
+ }
6580
+ function minuteWindow(now) {
6581
+ const start = new Date(now);
6582
+ start.setSeconds(0, 0);
6583
+ return { start, end: new Date(start.getTime() + 60000) };
6584
+ }
6585
+ function isScheduleExpressionDue(expression, now = new Date) {
6586
+ const { start, end } = minuteWindow(now);
6587
+ const next = parseScheduleExpression(expression, new Date(start.getTime() - 1));
6588
+ if (!next) {
6589
+ return false;
6590
+ }
6591
+ const timestamp = next.getTime();
6592
+ return timestamp >= start.getTime() && timestamp < end.getTime();
6593
+ }
6594
+ function assertScheduleExpression(expression) {
6595
+ try {
6596
+ const next = parseScheduleExpression(expression);
6597
+ if (!(next instanceof Date) || Number.isNaN(next.getTime())) {
6598
+ throw new Error("unparsable");
6599
+ }
6600
+ } catch {
6601
+ throw new Error(`Unsupported schedule expression "${expression}".`);
6563
6602
  }
6564
- return /^(\*\/\d+)( \*){4}$/.test(expression);
6565
6603
  }
6566
6604
 
6605
+ // ../../src/core/scheduler/schedule.ts
6567
6606
  class Schedule {
6568
6607
  tasks = [];
6569
6608
  command(expression, name, run) {
6570
- if (!isSupportedScheduleExpression(expression)) {
6571
- throw new Error(`Unsupported schedule expression "${expression}". Only "* * * * *" and "*/N * * * *" are implemented.`);
6572
- }
6609
+ assertScheduleExpression(expression);
6573
6610
  this.tasks.push({ expression, name, run });
6574
6611
  return this;
6575
6612
  }
6576
6613
  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
- });
6614
+ return this.tasks.filter((task) => isScheduleExpressionDue(task.expression, now));
6589
6615
  }
6590
6616
  tasksList() {
6591
6617
  return [...this.tasks];
@@ -6690,8 +6716,9 @@ function createTenantMiddleware() {
6690
6716
  });
6691
6717
  });
6692
6718
  } catch (error) {
6693
- if (error instanceof HttpError) {
6694
- return Response.json({ error: error.message }, { status: error.status });
6719
+ const httpError = toHttpError(error);
6720
+ if (httpError) {
6721
+ return Response.json({ error: httpError.message }, { status: httpError.status });
6695
6722
  }
6696
6723
  throw error;
6697
6724
  }
@@ -7300,6 +7327,7 @@ export {
7300
7327
  isEtagEnabled,
7301
7328
  isGlobalAdmin,
7302
7329
  isHtmxRequest,
7330
+ isHttpErrorLike,
7303
7331
  isInsideTenantDatabaseScope,
7304
7332
  isPublicReadsEnabled,
7305
7333
  isTenancyEnabled,
@@ -7362,6 +7390,7 @@ export {
7362
7390
  resolveCsrfTokenForRequest,
7363
7391
  resolveDatabaseDriver,
7364
7392
  resolveHtmlContentSecurityPolicy,
7393
+ resolveMembershipLookup,
7365
7394
  resolveMembershipService,
7366
7395
  resolveOrganizationScope,
7367
7396
  resolveRepositoryConnection,
@@ -7378,6 +7407,7 @@ export {
7378
7407
  runSeedersFromDirectory,
7379
7408
  runWithAuthUser,
7380
7409
  runWithDatabaseConnection,
7410
+ runWithMembershipContext,
7381
7411
  runWithRequestMeta,
7382
7412
  runWithTenant,
7383
7413
  runWithTenantDatabase,
@@ -7400,6 +7430,7 @@ export {
7400
7430
  stripMarkdown,
7401
7431
  temporarySignedUrl,
7402
7432
  textResponse,
7433
+ toHttpError,
7403
7434
  toPaginatedResourceCollection,
7404
7435
  toResourceCollection,
7405
7436
  trustForwardedFor,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getstrata/core",
3
- "version": "0.5.90",
3
+ "version": "0.5.97",
4
4
  "description": "Strata — Laravel-inspired Bun framework public API",
5
5
  "type": "module",
6
6
  "license": "MIT",