@getstrata/core 1.1.0 → 1.1.2

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
+ ## 1.1.2
4
+
5
+ - Product CLI helpers, file migrations, and job discovery.
6
+
7
+ ## Unreleased
8
+
9
+ - `Job` tracks `static jobName` on construct so `queue.dispatch(new FooJob(), payload)` resolves a registry name.
10
+ - The core migration runner is dialect-aware (placeholders, upsert, returning, timestamp column) so sqlite and mysql product apps can use `framework_migrations`. Import it from `@getstrata/core/database/migrations`.
11
+
12
+ ## 1.1.1
13
+
14
+ Fix generated app boot
15
+
3
16
  ## 1.1.0
4
17
 
5
18
  Breaking security hardening. Claims below match the code.
@@ -47,7 +60,6 @@ Breaking security hardening. Claims below match the code.
47
60
  - OpenAPI documents registered routes only. Generated `docs/API.md` lists the layer's live paths and does not include leftover `/webhooks` or `/billing` strings.
48
61
  - Root Compose Redis requires `dev-redis-change-me`. Root Compose Postgres uses `dev-postgres-change-me`. Root Compose MySQL uses `dev-mysql-change-me`. Host helper `scripts/with-host-env.sh` uses those passwords and points HiroApp `APP_DATABASE_URL` at `strata_app` / `dev-strata-app-change-me`. Fixture `DATABASE_URL` stays fixture admin for `bun_testing_test`. Production Compose Redis requires `REDIS_PASSWORD` and requires `APP_ENV` to be set. Production Compose `app`/`worker` runtime `DATABASE_URL` and `APP_DATABASE_URL` are `strata_app` after the split. `MIGRATION_DATABASE_URL` stays `${POSTGRES_USER}` for migrate. `STRATA_APP_PASSWORD` is required the same way `REDIS_PASSWORD` is. Bind stays `127.0.0.1:3000`. `127.0.0.1:54329` / `6379` / `33061` stay published. Adminer is debug-profile only. Prod compose file test plus HiroApp live-role e2e. This CI does not compose-up `docker-compose.prod.yml`.
49
62
 
50
-
51
63
  ## 1.0.9
52
64
 
53
65
  Label HiroApp as internal e2e dogfood and seed notes via Model
@@ -1,8 +1,10 @@
1
1
  type QueuePriority = "high" | "default" | "low";
2
2
  declare abstract class Job<TPayload extends object = object> {
3
+ static readonly jobName?: string;
3
4
  readonly maxAttempts?: number;
4
5
  readonly backoffMs?: number;
5
6
  readonly priority?: QueuePriority;
7
+ constructor();
6
8
  abstract handle(payload: TPayload): Promise<void>;
7
9
  }
8
10
  interface Queue {
@@ -4,6 +4,198 @@ import { readdir } from "fs/promises";
4
4
  import { join } from "path";
5
5
  import { pathToFileURL } from "url";
6
6
 
7
+ // ../../src/core/runtime/asyncContextStore.ts
8
+ import { AsyncLocalStorage } from "async_hooks";
9
+ function createAsyncContextStore(key) {
10
+ const symbol = Symbol.for(key);
11
+ const globalRecord = globalThis;
12
+ const existing = globalRecord[symbol];
13
+ if (existing) {
14
+ return existing;
15
+ }
16
+ const store = new AsyncLocalStorage;
17
+ globalRecord[symbol] = store;
18
+ return store;
19
+ }
20
+
21
+ // ../../src/core/database/schema/driver.ts
22
+ function normalizeConnectionName(connection) {
23
+ const normalized = connection.trim().toLowerCase();
24
+ if (normalized === "pgsql" || normalized === "postgres" || normalized === "postgresql") {
25
+ return "pgsql";
26
+ }
27
+ if (normalized === "mysql" || normalized === "mariadb") {
28
+ return "mysql";
29
+ }
30
+ if (normalized === "sqlite") {
31
+ return "sqlite";
32
+ }
33
+ throw new Error(`Unsupported DB_CONNECTION: ${connection}`);
34
+ }
35
+ function resolveDriverFromUrl(url) {
36
+ const normalized = url.trim().toLowerCase();
37
+ if (normalized.startsWith("postgres://") || normalized.startsWith("postgresql://") || normalized.startsWith("postgres:")) {
38
+ return "pgsql";
39
+ }
40
+ if (normalized.startsWith("mysql://") || normalized.startsWith("mysql:")) {
41
+ return "mysql";
42
+ }
43
+ if (normalized.startsWith("sqlite:")) {
44
+ return "sqlite";
45
+ }
46
+ return null;
47
+ }
48
+ function resolveDatabaseDriver(options = {}) {
49
+ const connection = options.connection ?? process.env.DB_CONNECTION;
50
+ if (connection) {
51
+ return normalizeConnectionName(connection);
52
+ }
53
+ const url = options.url ?? process.env.DATABASE_URL ?? "";
54
+ const fromUrl = resolveDriverFromUrl(url);
55
+ if (fromUrl) {
56
+ return fromUrl;
57
+ }
58
+ return "pgsql";
59
+ }
60
+
61
+ // ../../src/core/database/dialect.ts
62
+ function assertSafeIdentifier(identifier) {
63
+ if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(identifier)) {
64
+ throw new Error(`Invalid SQL identifier: ${identifier}`);
65
+ }
66
+ return identifier;
67
+ }
68
+ function quoteIdentifierFor(dialect, column) {
69
+ return dialect.quoteIdentifier(column);
70
+ }
71
+ var postgresDialect = {
72
+ driver: "pgsql",
73
+ placeholder(index) {
74
+ return `$${index}`;
75
+ },
76
+ quoteIdentifier(identifier) {
77
+ return `"${assertSafeIdentifier(identifier)}"`;
78
+ },
79
+ nowExpression() {
80
+ return "NOW()";
81
+ },
82
+ timestampValue(value) {
83
+ return value.toISOString();
84
+ },
85
+ returningClause(columns) {
86
+ return ` RETURNING ${columns}`;
87
+ },
88
+ ilikeOperator() {
89
+ return "ILIKE";
90
+ },
91
+ nullsLastSuffix() {
92
+ return " NULLS LAST";
93
+ },
94
+ castToText(expression) {
95
+ return `${expression}::text`;
96
+ },
97
+ upsertSuffix(conflictColumns, updateColumns) {
98
+ const target = conflictColumns.map((column) => quoteIdentifierFor(this, column)).join(", ");
99
+ if (updateColumns.length === 0) {
100
+ return ` ON CONFLICT (${target}) DO NOTHING`;
101
+ }
102
+ const assignments = updateColumns.map((column) => {
103
+ const quoted = quoteIdentifierFor(this, column);
104
+ return `${quoted} = excluded.${quoted}`;
105
+ }).join(", ");
106
+ return ` ON CONFLICT (${target}) DO UPDATE SET ${assignments}`;
107
+ }
108
+ };
109
+ var mysqlDialect = {
110
+ driver: "mysql",
111
+ placeholder() {
112
+ return "?";
113
+ },
114
+ quoteIdentifier(identifier) {
115
+ return `\`${assertSafeIdentifier(identifier)}\``;
116
+ },
117
+ nowExpression() {
118
+ return "CURRENT_TIMESTAMP";
119
+ },
120
+ timestampValue(value) {
121
+ return value.toISOString().slice(0, 19).replace("T", " ");
122
+ },
123
+ returningClause() {
124
+ return "";
125
+ },
126
+ ilikeOperator() {
127
+ return "LIKE";
128
+ },
129
+ nullsLastSuffix() {
130
+ return "";
131
+ },
132
+ castToText(expression) {
133
+ return `CAST(${expression} AS CHAR)`;
134
+ },
135
+ upsertSuffix(conflictColumns, updateColumns) {
136
+ if (updateColumns.length === 0) {
137
+ const anchor = quoteIdentifierFor(this, conflictColumns[0] ?? "");
138
+ return ` ON DUPLICATE KEY UPDATE ${anchor} = ${anchor}`;
139
+ }
140
+ const assignments = updateColumns.map((column) => {
141
+ const quoted = quoteIdentifierFor(this, column);
142
+ return `${quoted} = VALUES(${quoted})`;
143
+ }).join(", ");
144
+ return ` ON DUPLICATE KEY UPDATE ${assignments}`;
145
+ }
146
+ };
147
+ var sqliteDialect = {
148
+ driver: "sqlite",
149
+ placeholder() {
150
+ return "?";
151
+ },
152
+ quoteIdentifier(identifier) {
153
+ return `"${assertSafeIdentifier(identifier)}"`;
154
+ },
155
+ nowExpression() {
156
+ return "strftime('%Y-%m-%dT%H:%M:%fZ', 'now')";
157
+ },
158
+ timestampValue(value) {
159
+ return value.toISOString();
160
+ },
161
+ returningClause(columns) {
162
+ return ` RETURNING ${columns}`;
163
+ },
164
+ ilikeOperator() {
165
+ return "LIKE";
166
+ },
167
+ nullsLastSuffix() {
168
+ return "";
169
+ },
170
+ castToText(expression) {
171
+ return `CAST(${expression} AS TEXT)`;
172
+ },
173
+ upsertSuffix(conflictColumns, updateColumns) {
174
+ const target = conflictColumns.map((column) => quoteIdentifierFor(this, column)).join(", ");
175
+ if (updateColumns.length === 0) {
176
+ return ` ON CONFLICT (${target}) DO NOTHING`;
177
+ }
178
+ const assignments = updateColumns.map((column) => {
179
+ const quoted = quoteIdentifierFor(this, column);
180
+ return `${quoted} = excluded.${quoted}`;
181
+ }).join(", ");
182
+ return ` ON CONFLICT (${target}) DO UPDATE SET ${assignments}`;
183
+ }
184
+ };
185
+ var dialects = {
186
+ pgsql: postgresDialect,
187
+ mysql: mysqlDialect,
188
+ sqlite: sqliteDialect
189
+ };
190
+ var dialectContext = createAsyncContextStore("@getstrata/sqlDialect");
191
+ var dialectOverride = null;
192
+ function dialectFor(driver) {
193
+ return dialects[driver];
194
+ }
195
+ function currentSqlDialect() {
196
+ return dialectContext.getStore() ?? dialectOverride ?? dialectFor(resolveDatabaseDriver());
197
+ }
198
+
7
199
  // ../../src/core/database/migrations/advisoryLock.ts
8
200
  var MIGRATION_LOCK_KEY = 42424242;
9
201
  async function withMigrationLock(db, callback, lockKey = MIGRATION_LOCK_KEY) {
@@ -17,15 +209,37 @@ async function withMigrationLock(db, callback, lockKey = MIGRATION_LOCK_KEY) {
17
209
 
18
210
  // ../../src/core/database/migrations/runner.ts
19
211
  var MIGRATIONS_TABLE = "framework_migrations";
212
+ function migrationsNameColumn() {
213
+ return currentSqlDialect().driver === "mysql" ? "VARCHAR(255) PRIMARY KEY" : "TEXT PRIMARY KEY";
214
+ }
215
+ function migrationsRunOnColumn() {
216
+ const dialect = currentSqlDialect();
217
+ if (dialect.driver === "pgsql") {
218
+ return `TIMESTAMPTZ NOT NULL DEFAULT ${dialect.nowExpression()}`;
219
+ }
220
+ if (dialect.driver === "mysql") {
221
+ return `DATETIME NOT NULL DEFAULT ${dialect.nowExpression()}`;
222
+ }
223
+ return "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP";
224
+ }
20
225
  async function ensureMigrationsTable(db) {
21
226
  await db.unsafe(`
22
227
  CREATE TABLE IF NOT EXISTS ${MIGRATIONS_TABLE} (
23
- name TEXT PRIMARY KEY,
228
+ name ${migrationsNameColumn()},
24
229
  batch INTEGER NOT NULL,
25
- run_on TIMESTAMPTZ NOT NULL DEFAULT NOW()
230
+ run_on ${migrationsRunOnColumn()}
26
231
  )
27
232
  `);
28
233
  }
234
+ async function recordAppliedMigration(db, name, batch) {
235
+ const dialect = currentSqlDialect();
236
+ const inserted = await db.unsafe(`INSERT INTO ${MIGRATIONS_TABLE} (name, batch) VALUES (${dialect.placeholder(1)}, ${dialect.placeholder(2)})${dialect.upsertSuffix(["name"], [])}${dialect.returningClause("name")}`, [name, batch]);
237
+ if (dialect.returningClause("name")) {
238
+ return inserted.length > 0;
239
+ }
240
+ const rows = await db.unsafe(`SELECT batch FROM ${MIGRATIONS_TABLE} WHERE name = ${dialect.placeholder(1)}`, [name]);
241
+ return rows.some((row) => Number(row.batch) === batch);
242
+ }
29
243
  async function getAppliedMigrations(db) {
30
244
  await ensureMigrationsTable(db);
31
245
  return await db.unsafe(`
@@ -61,8 +275,8 @@ async function runPendingMigrations(db, migrations, options = {}) {
61
275
  for (const migration of pendingMigrations) {
62
276
  options.onMigration?.(migration.name);
63
277
  await migration.up(db);
64
- const inserted = await db.unsafe(`INSERT INTO ${MIGRATIONS_TABLE} (name, batch) VALUES ($1, $2) ON CONFLICT (name) DO NOTHING RETURNING name`, [migration.name, nextBatch]);
65
- if (inserted.length === 0) {
278
+ const recorded = await recordAppliedMigration(db, migration.name, nextBatch);
279
+ if (!recorded) {
66
280
  throw new Error(`Migration ${migration.name} was applied but not recorded.`);
67
281
  }
68
282
  }
@@ -89,7 +303,7 @@ async function rollbackDatabase(db, migrations, options = {}) {
89
303
  }
90
304
  options.onMigration?.(migration.name);
91
305
  await migration.down(db);
92
- await db.unsafe(`DELETE FROM ${MIGRATIONS_TABLE} WHERE name = $1`, [migration.name]);
306
+ await db.unsafe(`DELETE FROM ${MIGRATIONS_TABLE} WHERE name = ${currentSqlDialect().placeholder(1)}`, [migration.name]);
93
307
  rolledBack += 1;
94
308
  }
95
309
  return rolledBack;
@@ -615,11 +615,55 @@ class Logger {
615
615
  }
616
616
  var appLogger = new Logger("app");
617
617
 
618
+ // ../../src/core/queue/jobRegistry.ts
619
+ class JobRegistry {
620
+ factories = new Map;
621
+ instances = new WeakMap;
622
+ register(name, factory) {
623
+ this.factories.set(name, factory);
624
+ }
625
+ resolveName(job) {
626
+ return this.instances.get(job);
627
+ }
628
+ track(name, job) {
629
+ this.instances.set(job, name);
630
+ return job;
631
+ }
632
+ create(name) {
633
+ const factory = this.factories.get(name);
634
+ if (!factory) {
635
+ return;
636
+ }
637
+ return factory();
638
+ }
639
+ names() {
640
+ return [...this.factories.keys()];
641
+ }
642
+ }
643
+ var JOB_REGISTRY_KEY = Symbol.for("@getstrata/jobRegistry");
644
+ function readSharedJobRegistry() {
645
+ const globalRegistry = globalThis[JOB_REGISTRY_KEY];
646
+ if (globalRegistry) {
647
+ return globalRegistry;
648
+ }
649
+ const registry = new JobRegistry;
650
+ globalThis[JOB_REGISTRY_KEY] = registry;
651
+ return registry;
652
+ }
653
+ var jobRegistry = readSharedJobRegistry();
654
+
618
655
  // ../../src/core/queue/index.ts
619
656
  class Job {
657
+ static jobName;
620
658
  maxAttempts;
621
659
  backoffMs;
622
660
  priority;
661
+ constructor() {
662
+ const jobName = this.constructor.jobName;
663
+ if (jobName) {
664
+ jobRegistry.track(jobName, this);
665
+ }
666
+ }
623
667
  }
624
668
 
625
669
  class SyncQueue {
@@ -1,9 +1,53 @@
1
1
  // @bun
2
+ // ../../src/core/queue/jobRegistry.ts
3
+ class JobRegistry {
4
+ factories = new Map;
5
+ instances = new WeakMap;
6
+ register(name, factory) {
7
+ this.factories.set(name, factory);
8
+ }
9
+ resolveName(job) {
10
+ return this.instances.get(job);
11
+ }
12
+ track(name, job) {
13
+ this.instances.set(job, name);
14
+ return job;
15
+ }
16
+ create(name) {
17
+ const factory = this.factories.get(name);
18
+ if (!factory) {
19
+ return;
20
+ }
21
+ return factory();
22
+ }
23
+ names() {
24
+ return [...this.factories.keys()];
25
+ }
26
+ }
27
+ var JOB_REGISTRY_KEY = Symbol.for("@getstrata/jobRegistry");
28
+ function readSharedJobRegistry() {
29
+ const globalRegistry = globalThis[JOB_REGISTRY_KEY];
30
+ if (globalRegistry) {
31
+ return globalRegistry;
32
+ }
33
+ const registry = new JobRegistry;
34
+ globalThis[JOB_REGISTRY_KEY] = registry;
35
+ return registry;
36
+ }
37
+ var jobRegistry = readSharedJobRegistry();
38
+
2
39
  // ../../src/core/queue/index.ts
3
40
  class Job {
41
+ static jobName;
4
42
  maxAttempts;
5
43
  backoffMs;
6
44
  priority;
45
+ constructor() {
46
+ const jobName = this.constructor.jobName;
47
+ if (jobName) {
48
+ jobRegistry.track(jobName, this);
49
+ }
50
+ }
7
51
  }
8
52
 
9
53
  class SyncQueue {
@@ -1,9 +1,53 @@
1
1
  // @bun
2
+ // ../../src/core/queue/jobRegistry.ts
3
+ class JobRegistry {
4
+ factories = new Map;
5
+ instances = new WeakMap;
6
+ register(name, factory) {
7
+ this.factories.set(name, factory);
8
+ }
9
+ resolveName(job) {
10
+ return this.instances.get(job);
11
+ }
12
+ track(name, job) {
13
+ this.instances.set(job, name);
14
+ return job;
15
+ }
16
+ create(name) {
17
+ const factory = this.factories.get(name);
18
+ if (!factory) {
19
+ return;
20
+ }
21
+ return factory();
22
+ }
23
+ names() {
24
+ return [...this.factories.keys()];
25
+ }
26
+ }
27
+ var JOB_REGISTRY_KEY = Symbol.for("@getstrata/jobRegistry");
28
+ function readSharedJobRegistry() {
29
+ const globalRegistry = globalThis[JOB_REGISTRY_KEY];
30
+ if (globalRegistry) {
31
+ return globalRegistry;
32
+ }
33
+ const registry = new JobRegistry;
34
+ globalThis[JOB_REGISTRY_KEY] = registry;
35
+ return registry;
36
+ }
37
+ var jobRegistry = readSharedJobRegistry();
38
+
2
39
  // ../../src/core/queue/index.ts
3
40
  class Job {
41
+ static jobName;
4
42
  maxAttempts;
5
43
  backoffMs;
6
44
  priority;
45
+ constructor() {
46
+ const jobName = this.constructor.jobName;
47
+ if (jobName) {
48
+ jobRegistry.track(jobName, this);
49
+ }
50
+ }
7
51
  }
8
52
 
9
53
  class SyncQueue {
package/dist/index.js CHANGED
@@ -5082,15 +5082,37 @@ import { readdir } from "fs/promises";
5082
5082
  import { join } from "path";
5083
5083
  import { pathToFileURL } from "url";
5084
5084
  var MIGRATIONS_TABLE = "framework_migrations";
5085
+ function migrationsNameColumn() {
5086
+ return currentSqlDialect().driver === "mysql" ? "VARCHAR(255) PRIMARY KEY" : "TEXT PRIMARY KEY";
5087
+ }
5088
+ function migrationsRunOnColumn() {
5089
+ const dialect = currentSqlDialect();
5090
+ if (dialect.driver === "pgsql") {
5091
+ return `TIMESTAMPTZ NOT NULL DEFAULT ${dialect.nowExpression()}`;
5092
+ }
5093
+ if (dialect.driver === "mysql") {
5094
+ return `DATETIME NOT NULL DEFAULT ${dialect.nowExpression()}`;
5095
+ }
5096
+ return "TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP";
5097
+ }
5085
5098
  async function ensureMigrationsTable(db) {
5086
5099
  await db.unsafe(`
5087
5100
  CREATE TABLE IF NOT EXISTS ${MIGRATIONS_TABLE} (
5088
- name TEXT PRIMARY KEY,
5101
+ name ${migrationsNameColumn()},
5089
5102
  batch INTEGER NOT NULL,
5090
- run_on TIMESTAMPTZ NOT NULL DEFAULT NOW()
5103
+ run_on ${migrationsRunOnColumn()}
5091
5104
  )
5092
5105
  `);
5093
5106
  }
5107
+ async function recordAppliedMigration(db, name, batch) {
5108
+ const dialect = currentSqlDialect();
5109
+ const inserted = await db.unsafe(`INSERT INTO ${MIGRATIONS_TABLE} (name, batch) VALUES (${dialect.placeholder(1)}, ${dialect.placeholder(2)})${dialect.upsertSuffix(["name"], [])}${dialect.returningClause("name")}`, [name, batch]);
5110
+ if (dialect.returningClause("name")) {
5111
+ return inserted.length > 0;
5112
+ }
5113
+ const rows = await db.unsafe(`SELECT batch FROM ${MIGRATIONS_TABLE} WHERE name = ${dialect.placeholder(1)}`, [name]);
5114
+ return rows.some((row) => Number(row.batch) === batch);
5115
+ }
5094
5116
  async function getAppliedMigrations(db) {
5095
5117
  await ensureMigrationsTable(db);
5096
5118
  return await db.unsafe(`
@@ -5126,8 +5148,8 @@ async function runPendingMigrations(db, migrations, options = {}) {
5126
5148
  for (const migration of pendingMigrations) {
5127
5149
  options.onMigration?.(migration.name);
5128
5150
  await migration.up(db);
5129
- const inserted = await db.unsafe(`INSERT INTO ${MIGRATIONS_TABLE} (name, batch) VALUES ($1, $2) ON CONFLICT (name) DO NOTHING RETURNING name`, [migration.name, nextBatch]);
5130
- if (inserted.length === 0) {
5151
+ const recorded = await recordAppliedMigration(db, migration.name, nextBatch);
5152
+ if (!recorded) {
5131
5153
  throw new Error(`Migration ${migration.name} was applied but not recorded.`);
5132
5154
  }
5133
5155
  }
@@ -5154,7 +5176,7 @@ async function rollbackDatabase(db, migrations, options = {}) {
5154
5176
  }
5155
5177
  options.onMigration?.(migration.name);
5156
5178
  await migration.down(db);
5157
- await db.unsafe(`DELETE FROM ${MIGRATIONS_TABLE} WHERE name = $1`, [migration.name]);
5179
+ await db.unsafe(`DELETE FROM ${MIGRATIONS_TABLE} WHERE name = ${currentSqlDialect().placeholder(1)}`, [migration.name]);
5158
5180
  rolledBack += 1;
5159
5181
  }
5160
5182
  return rolledBack;
@@ -10387,31 +10409,6 @@ class Notification {
10387
10409
  return null;
10388
10410
  }
10389
10411
  }
10390
- // ../../src/core/queue/index.ts
10391
- class Job {
10392
- maxAttempts;
10393
- backoffMs;
10394
- priority;
10395
- }
10396
-
10397
- class SyncQueue {
10398
- async dispatch(job, payload) {
10399
- await job.handle(payload);
10400
- }
10401
- }
10402
-
10403
- class AsyncQueue {
10404
- async dispatch(job, payload) {
10405
- setTimeout(() => {
10406
- job.handle(payload).catch((error) => {
10407
- console.error("[AsyncQueue] Job failed:", error);
10408
- });
10409
- }, 0);
10410
- }
10411
- }
10412
- function createQueue(driver) {
10413
- return driver === "async" ? new AsyncQueue : new SyncQueue;
10414
- }
10415
10412
  // ../../src/core/queue/jobRegistry.ts
10416
10413
  class JobRegistry {
10417
10414
  factories = new Map;
@@ -10448,6 +10445,39 @@ function readSharedJobRegistry() {
10448
10445
  return registry;
10449
10446
  }
10450
10447
  var jobRegistry = readSharedJobRegistry();
10448
+
10449
+ // ../../src/core/queue/index.ts
10450
+ class Job {
10451
+ static jobName;
10452
+ maxAttempts;
10453
+ backoffMs;
10454
+ priority;
10455
+ constructor() {
10456
+ const jobName = this.constructor.jobName;
10457
+ if (jobName) {
10458
+ jobRegistry.track(jobName, this);
10459
+ }
10460
+ }
10461
+ }
10462
+
10463
+ class SyncQueue {
10464
+ async dispatch(job, payload) {
10465
+ await job.handle(payload);
10466
+ }
10467
+ }
10468
+
10469
+ class AsyncQueue {
10470
+ async dispatch(job, payload) {
10471
+ setTimeout(() => {
10472
+ job.handle(payload).catch((error) => {
10473
+ console.error("[AsyncQueue] Job failed:", error);
10474
+ });
10475
+ }, 0);
10476
+ }
10477
+ }
10478
+ function createQueue(driver) {
10479
+ return driver === "async" ? new AsyncQueue : new SyncQueue;
10480
+ }
10451
10481
  // ../../src/core/queue/failedJobTable.ts
10452
10482
  var failedJobTable = defineTable({
10453
10483
  name: "failed_job",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getstrata/core",
3
- "version": "1.1.0",
3
+ "version": "1.1.2",
4
4
  "description": "Strata Bun framework public API",
5
5
  "type": "module",
6
6
  "license": "MIT",