@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 +1 -1
- package/dist/cli.js +257 -90
- package/dist/templates/package.json +4 -3
- package/dist/templates/strata.config.ts +1 -0
- package/package.json +1 -1
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.
|
|
2860
|
-
"@getstrata/cli": "^1.1.
|
|
2861
|
-
"@getstrata/core": "^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
|
|
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
|
-
|
|
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 =
|
|
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}${
|
|
3620
|
-
|
|
3621
|
-
|
|
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)}
|
|
3634
|
-
|
|
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
|
-
|
|
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
|
-
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
|
|
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)}
|
|
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
|
-
|
|
3937
|
-
|
|
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
|
|
3943
|
-
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
|
|
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
|
-
|
|
3964
|
-
|
|
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)}
|
|
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
|
|
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 {
|
|
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,
|
|
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.
|
|
15
|
-
"@getstrata/cli": "^1.1.
|
|
16
|
-
"@getstrata/core": "^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": {
|