@getstrata/starter 1.1.1 → 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/README.md CHANGED
@@ -34,7 +34,7 @@ Pick one database engine. Docker Compose is optional (`--docker`, `--no-docker`,
34
34
 
35
35
  A Bun and TypeScript app on `@getstrata/core` and `@getstrata/bootstrap` that boots, migrates, and passes `bun run check`. Cookie apps include a restyleable welcome page, `/login`, `/register`, and password reset. Token apps include `POST /api/v1/auth/login` plus register and reset. Tenancy is `none`, `column` (any engine), or `rls` (Postgres only; other engines fall back to `column`). Choices are recorded in `strata.layers.json`.
36
36
 
37
- The `strata` binary installs into the app rather than globally, so use the generated `bun run` scripts.
37
+ The `strata` binary installs into the app rather than globally, so use the generated `bun run` scripts. Generated apps ship `src/cli/register.ts` with `queue:work` (`bun run queue:work`, requires `REDIS_URL`), `make:*`, `openapi:*`, and `schedule:run`. `queue:work` boots that app rather than the framework-repo worker.
38
38
 
39
39
  ## Docs
40
40
 
package/dist/cli.js CHANGED
@@ -2856,9 +2856,9 @@ function renderPackageJson(projectName, options = {}) {
2856
2856
  "@getstrata/cli": "workspace:*",
2857
2857
  "@getstrata/core": "workspace:*"
2858
2858
  } : {
2859
- "@getstrata/bootstrap": "^1.1.1",
2860
- "@getstrata/cli": "^1.1.1",
2861
- "@getstrata/core": "^1.1.1"
2859
+ "@getstrata/bootstrap": "^1.1.2",
2860
+ "@getstrata/cli": "^1.1.2",
2861
+ "@getstrata/core": "^1.1.2"
2862
2862
  };
2863
2863
  coreDeps.eta = "^4.6.0";
2864
2864
  if (options.layers?.database === "mysql") {
@@ -2869,6 +2869,7 @@ function renderPackageJson(projectName, options = {}) {
2869
2869
  start: "strata start",
2870
2870
  "db:migrate": "strata migrate",
2871
2871
  "db:fresh": "strata migrate:fresh",
2872
+ "queue:work": "strata queue:work",
2872
2873
  check: "tsc --noEmit"
2873
2874
  };
2874
2875
  if (options.layers && needsFrontendBuild(options.layers.frontend)) {
@@ -3081,7 +3082,7 @@ ${next.join(`
3081
3082
  `)}
3082
3083
  \`\`\`
3083
3084
 
3084
- Open http://localhost:3000. Health check: \`GET /health\`.
3085
+ Open http://localhost:3000. Health check: \`GET /health\`. Redis worker: \`bun run queue:work\` (requires \`REDIS_URL\`).
3085
3086
 
3086
3087
  ${renderSupportingToolsReadme(layers)}${layers.auth !== "headers" ? `
3087
3088
  Seeded login (password \`StrataDemo!ChangeMe\`):
@@ -3146,7 +3147,7 @@ The image sets \`APP_ENV=production\` and \`AUTH_DEV_HEADERS=false\`; everything
3146
3147
  - Behind a reverse proxy or load balancer, set \`TRUST_FORWARDED_FOR=true\` so throttles and session records see the client address instead of the proxy. Only the rightmost public hop of \`X-Forwarded-For\` is trusted.
3147
3148
  ${authUsesCookie(layers.auth) ? "- Set `SESSION_SECRET` to 32+ characters.\n" : ""}${authUsesToken(layers.auth) ? "- Set `TOKEN_HASH_PEPPER`.\n" : ""}${layers.extras.scim ? "- Set `SCIM_BEARER_TOKEN`.\n" : ""}${layers.extras.metrics ? "- Set `METRICS_TOKEN`.\n" : ""}${layers.tenancy === "rls" ? `- \`DATABASE_URL\` must be a \`NOBYPASSRLS\` role, not the \`postgres\` superuser. Generated apps create \`${GENERATED_POSTGRES_APP_ROLE}\` via \`db/ensure-postgres-app-role.sql\` (and Compose init on first empty volume). \`MIGRATION_DATABASE_URL\` may stay the superuser for CREATE ROLE / GRANT / migrate. Production boot rejects username \`postgres\` or \`root\`, then inspects \`pg_roles\` for \`rolsuper\` / \`rolbypassrls\`.
3148
3149
  ` : ""}
3149
- \`strata start\` does not migrate when \`APP_ENV=production\`. Run \`bun run db:migrate\` as a deploy step. \`GET /health\` is 200 when \`notes\` is readable (including zero rows) and 503 when that read fails.
3150
+ \`strata start\` does not migrate when \`APP_ENV=production\`. Run \`bun run db:migrate\` as a deploy step. \`GET /health\` is 200 when \`notes\` is readable (including zero rows) and 503 when that read fails. Redis jobs need a worker: \`bun run queue:work\` (requires \`REDIS_URL\`).
3150
3151
  `;
3151
3152
  }
3152
3153
 
@@ -3473,11 +3474,19 @@ export async function closeDatabase() {
3473
3474
  }
3474
3475
  `;
3475
3476
  }
3476
- function renderMigrateTs(layers) {
3477
+ function starterSchemaStatements(layers) {
3477
3478
  const d = dialectFragments(layers.database);
3478
3479
  const statements = [];
3479
3480
  const tenancyOn = usesTenantTable(layers.tenancy);
3480
3481
  const mfaOn = layers.extras.mfa && authNeedsUsers(layers.auth);
3482
+ const payloadType = layers.database === "postgres" ? "JSONB NOT NULL" : `${d.text} NOT NULL`;
3483
+ statements.push(`CREATE TABLE IF NOT EXISTS failed_job (
3484
+ id ${d.id},
3485
+ job_name ${d.keyText} NOT NULL,
3486
+ payload ${payloadType},
3487
+ exception ${d.text} NOT NULL,
3488
+ failed_at ${d.timestamp}
3489
+ )`);
3481
3490
  if (tenancyOn) {
3482
3491
  statements.push(`CREATE TABLE IF NOT EXISTS tenant (
3483
3492
  id ${d.id},
@@ -3564,8 +3573,87 @@ function renderMigrateTs(layers) {
3564
3573
  }
3565
3574
  statements.push(generatedRlsBootstrapSql(rlsTables, userOwnedTables).trim());
3566
3575
  }
3567
- const list = statements.map((sql) => ` \`${sql.replace(/`/g, "\\`")}\`,`).join(`
3576
+ return statements;
3577
+ }
3578
+ function renderStarterSchemaMigration(layers) {
3579
+ const statements = starterSchemaStatements(layers);
3580
+ const cascade = layers.database === "sqlite" ? "" : " CASCADE";
3581
+ const upCalls = statements.map((sql) => ` await db.unsafe(${JSON.stringify(sql)});`).join(`
3582
+ `);
3583
+ const downCalls = dropTables(layers).map((table) => ` await db.unsafe("DROP TABLE IF EXISTS ${table}${cascade}");`).join(`
3568
3584
  `);
3585
+ return `import type { Migration } from "@getstrata/core/database/migrations/types";
3586
+
3587
+ const migration: Migration = {
3588
+ name: "0001_starter_schema",
3589
+ async up(db) {
3590
+ ${upCalls}
3591
+ },
3592
+ async down(db) {
3593
+ ${downCalls}
3594
+ },
3595
+ };
3596
+
3597
+ export default migration;
3598
+ `;
3599
+ }
3600
+ function renderMigrationRuntimeTs(layers) {
3601
+ if (layers.database === "postgres") {
3602
+ return `import { join } from "node:path";
3603
+ import { loadMigrationsFromDirectory } from "@getstrata/core/database/migrations";
3604
+ import type { MigrationDatabase } from "@getstrata/core/database/migrations/types";
3605
+ import {
3606
+ grantPostgresAppRolePrivileges,
3607
+ openPostgresAdminConnection,
3608
+ postgresDatabaseNameFromUrl,
3609
+ } from "@getstrata/core/tenant/enableTenantRls";
3610
+
3611
+ const MIGRATIONS_DIRECTORY = join(import.meta.dir, "migrations");
3612
+
3613
+ export async function loadStarterMigrations() {
3614
+ return loadMigrationsFromDirectory(MIGRATIONS_DIRECTORY);
3615
+ }
3616
+
3617
+ export async function withMigrationDatabase<T>(
3618
+ fn: (db: MigrationDatabase) => Promise<T>,
3619
+ ): Promise<T> {
3620
+ const runtimeUrl = process.env.DATABASE_URL ?? "";
3621
+ const admin = await openPostgresAdminConnection({
3622
+ runtimeUrl,
3623
+ migrationUrl: process.env.MIGRATION_DATABASE_URL,
3624
+ });
3625
+ try {
3626
+ const result = await fn(admin);
3627
+ await grantPostgresAppRolePrivileges(admin, {
3628
+ database: postgresDatabaseNameFromUrl(runtimeUrl),
3629
+ });
3630
+ return result;
3631
+ } finally {
3632
+ await admin.close?.();
3633
+ }
3634
+ }
3635
+ `;
3636
+ }
3637
+ return `import { join } from "node:path";
3638
+ import { loadMigrationsFromDirectory } from "@getstrata/core/database/migrations";
3639
+ import type { MigrationDatabase } from "@getstrata/core/database/migrations/types";
3640
+ import { getSql } from "../bootstrap/database.ts";
3641
+
3642
+ const MIGRATIONS_DIRECTORY = join(import.meta.dir, "migrations");
3643
+
3644
+ export async function loadStarterMigrations() {
3645
+ return loadMigrationsFromDirectory(MIGRATIONS_DIRECTORY);
3646
+ }
3647
+
3648
+ export async function withMigrationDatabase<T>(
3649
+ fn: (db: MigrationDatabase) => Promise<T>,
3650
+ ): Promise<T> {
3651
+ return fn(getSql());
3652
+ }
3653
+ `;
3654
+ }
3655
+ function renderMigrateTs(layers) {
3656
+ const tenancyOn = usesTenantTable(layers.tenancy);
3569
3657
  const ph = layers.database === "postgres";
3570
3658
  const verifyOn = layers.extras.emailVerification && authNeedsUsers(layers.auth);
3571
3659
  const verifiedNow = nowTimestampLiteral(layers.database);
@@ -3600,8 +3688,6 @@ function renderMigrateTs(layers) {
3600
3688
  const hashImport = authNeedsUsers(layers.auth) ? `import { hashPassword } from "@getstrata/core/auth/password";
3601
3689
  ` : "";
3602
3690
  const rlsBypassImport = layers.tenancy === "rls" && layers.database === "postgres" ? `import { runWithMigrationBypass } from "@getstrata/core/tenant/databaseTenantContext";
3603
- ` : "";
3604
- const postgresRoleImport = layers.database === "postgres" ? `import { grantPostgresAppRolePrivileges, openPostgresAdminConnection, postgresDatabaseNameFromUrl } from "@getstrata/core/tenant/enableTenantRls";
3605
3691
  ` : "";
3606
3692
  const bindSql = seedTenant || layers.tenancy === "rls" && layers.database === "postgres" ? ` const sql = getSql();
3607
3693
  ` : "";
@@ -3612,15 +3698,13 @@ function renderMigrateTs(layers) {
3612
3698
  const noteCreate = tenancyOn ? `await Note.create({ body: "Welcome to Strata!", tenant_id: 1 });` : `await Note.create({ body: "Welcome to Strata!" });`;
3613
3699
  const userImport = authNeedsUsers(layers.auth) ? `import { User } from "../models/User.ts";
3614
3700
  ` : "";
3615
- const needsSqlClient = layers.database !== "postgres" || Boolean(seedTenant) || layers.tenancy === "rls" && layers.database === "postgres";
3701
+ const needsSqlClient = Boolean(seedTenant) || layers.tenancy === "rls" && layers.database === "postgres";
3616
3702
  const databaseImport = needsSqlClient ? `import { closeDatabase, getSql } from "../bootstrap/database.ts";
3617
3703
  ` : `import { closeDatabase } from "../bootstrap/database.ts";
3618
3704
  `;
3619
- return `${hashImport}${rlsBypassImport}${postgresRoleImport}${ensureImport(layers)}${databaseImport}import { Note } from "../models/Note.ts";
3620
- ${userImport}
3621
- const migrations = [
3622
- ${list}
3623
- ];
3705
+ return `${hashImport}${rlsBypassImport}${ensureImport(layers)}${databaseImport}import { migrateDatabase } from "@getstrata/core/database/migrations";
3706
+ import { Note } from "../models/Note.ts";
3707
+ ${userImport}import { loadStarterMigrations, withMigrationDatabase } from "./migrationRuntime.ts";
3624
3708
 
3625
3709
  export async function seed() {
3626
3710
  ${ensureCall(layers)}${bindSql}${seedOpen}${seedTenant}
@@ -3630,26 +3714,10 @@ ${ensureCall(layers)}${bindSql}${seedOpen}${seedTenant}
3630
3714
  }
3631
3715
 
3632
3716
  export async function migrate() {
3633
- ${ensureCall(layers)}${layers.database === "postgres" ? ` const runtimeUrl = process.env.DATABASE_URL ?? "";
3634
- const admin = await openPostgresAdminConnection({
3635
- runtimeUrl,
3636
- migrationUrl: process.env.MIGRATION_DATABASE_URL,
3717
+ ${ensureCall(layers)} await withMigrationDatabase(async (db) => {
3718
+ await migrateDatabase(db, await loadStarterMigrations());
3637
3719
  });
3638
- try {
3639
- for (const statement of migrations) {
3640
- await admin.unsafe(statement);
3641
- }
3642
- await grantPostgresAppRolePrivileges(admin, {
3643
- database: postgresDatabaseNameFromUrl(runtimeUrl),
3644
- });
3645
- } finally {
3646
- await admin.close?.();
3647
- }
3648
- ` : ` const sql = getSql();
3649
- for (const statement of migrations) {
3650
- await sql.unsafe(statement);
3651
- }
3652
- `} await seed();
3720
+ await seed();
3653
3721
  }
3654
3722
 
3655
3723
  /** The CLI calls this after migrate() so pooled drivers do not hold the process open. */
@@ -3862,7 +3930,7 @@ export { AuthOneTimeToken };
3862
3930
  `;
3863
3931
  }
3864
3932
  function dropTables(layers) {
3865
- const ordered = [];
3933
+ const ordered = ["failed_job"];
3866
3934
  if (authUsesToken(layers.auth)) {
3867
3935
  ordered.push("api_tokens");
3868
3936
  }
@@ -3880,31 +3948,17 @@ function dropTables(layers) {
3880
3948
  }
3881
3949
  return ordered;
3882
3950
  }
3883
- function renderPostgresAdminDropCall() {
3884
- return ` await dropPostgresTablesAsAdmin(tables, {
3885
- runtimeUrl: process.env.DATABASE_URL ?? "",
3886
- migrationUrl: process.env.MIGRATION_DATABASE_URL,
3887
- });
3888
- `;
3889
- }
3890
3951
  function renderFreshTs(layers) {
3891
- const tables = dropTables(layers);
3892
- const cascade = layers.database === "sqlite" ? "" : " CASCADE";
3893
- const imports = layers.database === "postgres" ? `import { dropPostgresTablesAsAdmin } from "@getstrata/core/tenant/enableTenantRls";
3894
- ${ensureImport(layers)}import { closeDatabase } from "../bootstrap/database.ts";
3895
- import { migrate } from "./migrate.ts";` : `${ensureImport(layers)}import { closeDatabase, getSql } from "../bootstrap/database.ts";
3896
- import { migrate } from "./migrate.ts";`;
3897
- const dropBody = layers.database === "postgres" ? `${renderPostgresAdminDropCall()} await migrate();` : ` const sql = getSql();
3898
- for (const table of tables) {
3899
- await sql.unsafe(\`DROP TABLE IF EXISTS \${table}${cascade}\`);
3900
- }
3901
- await migrate();`;
3902
- return `${imports}
3903
-
3904
- const tables = ${JSON.stringify(tables)};
3952
+ return `${ensureImport(layers)}import { freshDatabase } from "@getstrata/core/database/migrations";
3953
+ import { closeDatabase } from "../bootstrap/database.ts";
3954
+ import { seed } from "./migrate.ts";
3955
+ import { loadStarterMigrations, withMigrationDatabase } from "./migrationRuntime.ts";
3905
3956
 
3906
3957
  export async function fresh() {
3907
- ${ensureCall(layers)}${dropBody}
3958
+ ${ensureCall(layers)} await withMigrationDatabase(async (db) => {
3959
+ await freshDatabase(db, await loadStarterMigrations());
3960
+ });
3961
+ await seed();
3908
3962
  }
3909
3963
 
3910
3964
  /** The CLI calls this after fresh() so pooled drivers do not hold the process open. */
@@ -3933,23 +3987,17 @@ if (import.meta.main) {
3933
3987
  `;
3934
3988
  }
3935
3989
  function renderStatusTs(layers) {
3936
- const tables = dropTables(layers);
3937
- return `${ensureImport(layers)}import { getSql } from "../bootstrap/database.ts";
3938
-
3939
- const tables = ${JSON.stringify(tables)};
3990
+ return `${ensureImport(layers)}import { getMigrationStatus } from "@getstrata/core/database/migrations";
3991
+ import { loadStarterMigrations, withMigrationDatabase } from "./migrationRuntime.ts";
3940
3992
 
3941
3993
  export async function status() {
3942
- ${ensureCall(layers)} const sql = getSql();
3943
- console.log("Starter schema (inline SQL, not a migration runner):");
3944
- for (const table of tables) {
3945
- try {
3946
- const rows = await sql.unsafe<{ count: string | number }>(
3947
- \`SELECT COUNT(*) AS count FROM \${table}\`,
3948
- );
3949
- console.log(\`- [present] \${table} (rows: \${rows[0]?.count ?? 0})\`);
3950
- } catch {
3951
- console.log(\`- [missing] \${table}\`);
3952
- }
3994
+ ${ensureCall(layers)} const rows = await withMigrationDatabase(async (db) => {
3995
+ return getMigrationStatus(db, await loadStarterMigrations());
3996
+ });
3997
+ console.log("Migrations:");
3998
+ for (const row of rows) {
3999
+ const batch = row.batch == null ? "" : \` (batch \${row.batch})\`;
4000
+ console.log(\`- [\${row.status}] \${row.name}\${batch}\`);
3953
4001
  }
3954
4002
  }
3955
4003
 
@@ -3960,28 +4008,25 @@ if (import.meta.main) {
3960
4008
  `;
3961
4009
  }
3962
4010
  function renderRollbackTs(layers) {
3963
- const tables = dropTables(layers);
3964
- const cascade = layers.database === "sqlite" ? "" : " CASCADE";
3965
- const imports = layers.database === "postgres" ? `import { dropPostgresTablesAsAdmin } from "@getstrata/core/tenant/enableTenantRls";
3966
- ${ensureImport(layers)}` : `${ensureImport(layers)}import { getSql } from "../bootstrap/database.ts";`;
3967
- const dropBody = layers.database === "postgres" ? `${renderPostgresAdminDropCall()} for (const table of tables) {
3968
- console.log(\`dropped \${table}\`);
3969
- }` : ` const sql = getSql();
3970
- for (const table of tables) {
3971
- await sql.unsafe(\`DROP TABLE IF EXISTS \${table}${cascade}\`);
3972
- console.log(\`dropped \${table}\`);
3973
- }`;
3974
- return `${imports}
3975
-
3976
- const tables = ${JSON.stringify(tables)};
4011
+ return `${ensureImport(layers)}import { rollbackDatabase } from "@getstrata/core/database/migrations";
4012
+ import { loadStarterMigrations, withMigrationDatabase } from "./migrationRuntime.ts";
3977
4013
 
3978
4014
  export async function rollback() {
3979
- ${ensureCall(layers)}${dropBody}
4015
+ ${ensureCall(layers)} const rolledBack = await withMigrationDatabase(async (db) => {
4016
+ return rollbackDatabase(db, await loadStarterMigrations(), {
4017
+ onMigration: (name) => {
4018
+ console.log(\`rolled back \${name}\`);
4019
+ },
4020
+ });
4021
+ });
4022
+ if (rolledBack === 0) {
4023
+ console.log("Nothing to roll back.");
4024
+ }
3980
4025
  }
3981
4026
 
3982
4027
  if (import.meta.main) {
3983
4028
  await rollback();
3984
- console.log("Rolled back starter tables.");
4029
+ console.log("Rolled back last migration batch.");
3985
4030
  process.exit(0);
3986
4031
  }
3987
4032
  `;
@@ -4067,7 +4112,8 @@ export default configProvider;
4067
4112
  `;
4068
4113
  }
4069
4114
  function renderQueueProvider() {
4070
- return `import { registerDefaultJobs } from "@getstrata/bootstrap/queue/defaultJobs";
4115
+ return `import { discoverJobs } from "@getstrata/bootstrap/discoverJobs";
4116
+ import { registerDefaultJobs } from "@getstrata/bootstrap/queue/defaultJobs";
4071
4117
  import type { ServiceProvider } from "@getstrata/core/contracts/di";
4072
4118
  import { CORE_QUEUE_TOKEN } from "@getstrata/core/contracts/serviceTokens";
4073
4119
  import {
@@ -4084,7 +4130,10 @@ const queueProvider: ServiceProvider = {
4084
4130
  container.set(FAILED_JOB_SERVICE_TOKEN, failedJobs);
4085
4131
  container.set(
4086
4132
  CORE_QUEUE_TOKEN,
4087
- createAppQueue(driver, process.env.REDIS_URL, failedJobs, registerDefaultJobs),
4133
+ createAppQueue(driver, process.env.REDIS_URL, failedJobs, () => {
4134
+ registerDefaultJobs();
4135
+ discoverJobs();
4136
+ }),
4088
4137
  );
4089
4138
  },
4090
4139
  };
@@ -4392,6 +4441,116 @@ export function buildRoutes(dependencies: AppDependencies): AppRouteMap {
4392
4441
  }
4393
4442
  `;
4394
4443
  }
4444
+ function renderCliRegisterTs() {
4445
+ return `import type { StrataCommandMap } from "@getstrata/cli";
4446
+
4447
+ const commands: StrataCommandMap = {
4448
+ "make:module": async () => (await import("@getstrata/cli/scaffold")).makeModuleCommand,
4449
+ "make:policy": async () => (await import("@getstrata/cli/scaffold")).makePolicyCommand,
4450
+ "make:job": async () => (await import("@getstrata/cli/scaffold")).makeJobCommand,
4451
+ "make:listener": async () => (await import("@getstrata/cli/scaffold")).makeListenerCommand,
4452
+ "make:request": async () => (await import("@getstrata/cli/scaffold")).makeRequestCommand,
4453
+ "make:factory": async () => (await import("@getstrata/cli/scaffold")).makeFactoryCommand,
4454
+ "make:migration": async () => (await import("@getstrata/cli/scaffold")).makeMigrationCommand,
4455
+ "queue:work": async () => (await import("./queueWork.ts")).queueWorkCommand,
4456
+ "queue:failed": async () => (await import("./queueFailed.ts")).queueFailedCommand,
4457
+ "queue:retry": async () => (await import("./queueFailed.ts")).queueRetryCommand,
4458
+ "queue:flush-failed": async () => (await import("./queueFailed.ts")).queueFlushFailedCommand,
4459
+ "openapi:generate": async () => (await import("./openapi.ts")).openapiGenerateCommand,
4460
+ "openapi:validate": async () => (await import("./openapi.ts")).openapiValidateCommand,
4461
+ "openapi:check": async () => (await import("./openapi.ts")).openapiCheckCommand,
4462
+ "schedule:run": async () => (await import("./scheduleRun.ts")).scheduleRunCommand,
4463
+ };
4464
+
4465
+ export { commands };
4466
+ `;
4467
+ }
4468
+ function renderCliQueueWorkTs() {
4469
+ return `async function queueWorkCommand(): Promise<void> {
4470
+ if (!process.env.REDIS_URL) {
4471
+ throw new Error("queue:work requires REDIS_URL to be set.");
4472
+ }
4473
+
4474
+ const { runQueueWorkerCommand } = await import("@getstrata/cli/queueWorker");
4475
+ const { bootstrapApp } = await import("../bootstrap/createApp.ts");
4476
+ const { closeDatabase } = await import("../bootstrap/database.ts");
4477
+
4478
+ await runQueueWorkerCommand({
4479
+ boot: () => bootstrapApp({ migrate: false }),
4480
+ close: closeDatabase,
4481
+ });
4482
+ }
4483
+
4484
+ export { queueWorkCommand };
4485
+ `;
4486
+ }
4487
+ function renderCliQueueFailedTs() {
4488
+ return `async function bootApp(): Promise<void> {
4489
+ const { bootstrapApp } = await import("../bootstrap/createApp.ts");
4490
+ await bootstrapApp({ migrate: false });
4491
+ }
4492
+
4493
+ async function queueFailedCommand(): Promise<void> {
4494
+ const { queueFailedCommand: run } = await import("@getstrata/cli/queueFailed");
4495
+ await run(bootApp);
4496
+ }
4497
+
4498
+ async function queueRetryCommand(id?: string): Promise<void> {
4499
+ const { queueRetryCommand: run } = await import("@getstrata/cli/queueFailed");
4500
+ await run(id, bootApp);
4501
+ }
4502
+
4503
+ async function queueFlushFailedCommand(): Promise<void> {
4504
+ const { queueFlushFailedCommand: run } = await import("@getstrata/cli/queueFailed");
4505
+ await run(bootApp);
4506
+ }
4507
+
4508
+ export { queueFailedCommand, queueFlushFailedCommand, queueRetryCommand };
4509
+ `;
4510
+ }
4511
+ function renderCliOpenApiTs() {
4512
+ return `async function bootstrap() {
4513
+ const { createApp } = await import("../bootstrap/createApp.ts");
4514
+ return createApp({ migrate: false });
4515
+ }
4516
+
4517
+ async function openapiGenerateCommand(): Promise<void> {
4518
+ const { createOpenApiGenerateCommand } = await import("@getstrata/cli/openapi");
4519
+ await createOpenApiGenerateCommand(bootstrap)();
4520
+ }
4521
+
4522
+ async function openapiValidateCommand(): Promise<void> {
4523
+ const { createOpenApiValidateCommand } = await import("@getstrata/cli/openapi");
4524
+ await createOpenApiValidateCommand(bootstrap)();
4525
+ }
4526
+
4527
+ async function openapiCheckCommand(): Promise<void> {
4528
+ const { createOpenApiCheckCommand } = await import("@getstrata/cli/openapi");
4529
+ await createOpenApiCheckCommand(bootstrap)();
4530
+ }
4531
+
4532
+ export { openapiCheckCommand, openapiGenerateCommand, openapiValidateCommand };
4533
+ `;
4534
+ }
4535
+ function renderCliScheduleRunTs() {
4536
+ return `async function scheduleRunCommand(): Promise<void> {
4537
+ const { createScheduleRunCommand } = await import("@getstrata/cli/schedule");
4538
+ await createScheduleRunCommand(async () => {
4539
+ const { bootstrapApp } = await import("../bootstrap/createApp.ts");
4540
+ await bootstrapApp({ migrate: false });
4541
+ await import("../bootstrap/schedule.ts");
4542
+ })();
4543
+ }
4544
+
4545
+ export { scheduleRunCommand };
4546
+ `;
4547
+ }
4548
+ function renderScheduleTs() {
4549
+ return `import { appSchedule } from "@getstrata/core/scheduler/schedule";
4550
+
4551
+ export { appSchedule };
4552
+ `;
4553
+ }
4395
4554
  function renderViewTs(layers) {
4396
4555
  const authImport = authNeedsUsers(layers.auth) ? `import { currentAuthUser } from "@getstrata/core/auth/authContext";
4397
4556
  import { resolveCsrfTokenForRequest } from "@getstrata/core/http/csrfToken";
@@ -4841,6 +5000,12 @@ function writeGeneratedFiles(options) {
4841
5000
  removeIfExists(join2(src, "bootstrap/ensureDatabase.ts"));
4842
5001
  }
4843
5002
  writeText(join2(src, "bootstrap/createApp.ts"), renderCreateAppTs(layers));
5003
+ writeText(join2(src, "bootstrap/schedule.ts"), renderScheduleTs());
5004
+ writeText(join2(src, "cli/register.ts"), renderCliRegisterTs());
5005
+ writeText(join2(src, "cli/queueWork.ts"), renderCliQueueWorkTs());
5006
+ writeText(join2(src, "cli/queueFailed.ts"), renderCliQueueFailedTs());
5007
+ writeText(join2(src, "cli/openapi.ts"), renderCliOpenApiTs());
5008
+ writeText(join2(src, "cli/scheduleRun.ts"), renderCliScheduleRunTs());
4844
5009
  writeText(join2(src, "bootstrap/providers/config.ts"), renderConfigProvider(layers));
4845
5010
  writeText(join2(src, "bootstrap/providers/queue.ts"), renderQueueProvider());
4846
5011
  writeText(join2(src, "bootstrap/providers/policy.ts"), renderPolicyProvider());
@@ -4860,6 +5025,8 @@ function writeGeneratedFiles(options) {
4860
5025
  removeIfExists(join2(src, "models/ApiToken.ts"));
4861
5026
  }
4862
5027
  writeText(join2(src, "db/migrate.ts"), renderMigrateTs(layers));
5028
+ writeText(join2(src, "db/migrationRuntime.ts"), renderMigrationRuntimeTs(layers));
5029
+ writeText(join2(src, "db/migrations/0001_starter_schema.ts"), renderStarterSchemaMigration(layers));
4863
5030
  writeText(join2(src, "db/fresh.ts"), renderFreshTs(layers));
4864
5031
  writeText(join2(src, "db/seed.ts"), renderSeedTs());
4865
5032
  writeText(join2(src, "db/status.ts"), renderStatusTs(layers));
@@ -8,12 +8,13 @@
8
8
  "start": "strata start",
9
9
  "db:migrate": "strata migrate",
10
10
  "db:fresh": "strata migrate:fresh",
11
+ "queue:work": "strata queue:work",
11
12
  "check": "tsc --noEmit"
12
13
  },
13
14
  "dependencies": {
14
- "@getstrata/bootstrap": "^1.1.1",
15
- "@getstrata/cli": "^1.1.1",
16
- "@getstrata/core": "^1.1.1",
15
+ "@getstrata/bootstrap": "^1.1.2",
16
+ "@getstrata/cli": "^1.1.2",
17
+ "@getstrata/core": "^1.1.2",
17
18
  "eta": "^4.6.0"
18
19
  },
19
20
  "devDependencies": {
@@ -2,6 +2,7 @@ export default {
2
2
  preload: "./src/bootstrap/preload.ts",
3
3
  server: "./src/bootstrap/server.ts",
4
4
  modulesDirectory: "./src/modules",
5
+ commands: "./src/cli/register.ts",
5
6
  migrate: "./src/db/migrate.ts",
6
7
  fresh: "./src/db/fresh.ts",
7
8
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@getstrata/starter",
3
- "version": "1.1.1",
3
+ "version": "1.1.2",
4
4
  "description": "Interactive create-strata wizard. Choose each layer; one database engine.",
5
5
  "type": "module",
6
6
  "license": "MIT",