Skip to content

Commit 4576878

Browse files
Close DNA exceptions 1, 2, 5, 7, and 8 on the same PR.
Pin Alchemy 2.0.0-beta.72 and rewrite infra to stackOptions plus Cloudflare providers and local state. Move @solstatus/app from Next.js 15 + OpenNext to TanStack Start on Vite while keeping better-auth and the uptime dashboard. Add anti-slop as github:dmmulroy/anti-slop with warn-first oxlint rules. Keep Effect 4 CLI on effect/unstable/cli and map phases to alchemy deploy, destroy, plan, and dev. Add Alchemy Test/Vitest local-workerd coverage for monitor-exec. The release:none label could not be created (GitHub 403). Co-authored-by: Jonathan Beckman <jonathantbeckman@gmail.com>
1 parent bfdabe6 commit 4576878

75 files changed

Lines changed: 4855 additions & 4119 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,5 +41,10 @@ yarn-error.log*
4141
src/.alchemy
4242
**/.alchemy
4343

44+
# tanstack start / vite
45+
**/.output
46+
**/.tanstack
47+
**/.vinxi
48+
4449
# nub
4550
.nub/

.oxfmtrc.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@
66
"**/.next/**",
77
"**/.open-next/**",
88
"**/.wrangler/**",
9+
"**/.alchemy/**",
10+
"**/.output/**",
11+
"**/*.gen.ts",
912
"nub.lock",
1013
"packages/app/src/registry/**",
1114
"packages/common/src/db/migrations/**",
1215
"packages/app/cloudflare-env.d.ts",
13-
"packages/app/next-env.d.ts",
1416
"**/*.md",
1517
"**/*.yaml",
1618
"**/*.yml"

.oxlintrc.json

Lines changed: 39 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,14 @@
55
{
66
"name": "effect",
77
"specifier": "@mpsuesser/oxlint-plugin-effect"
8+
},
9+
{
10+
"name": "anti-slop",
11+
"specifier": "anti-slop"
12+
},
13+
{
14+
"name": "anti-slop-effect",
15+
"specifier": "./node_modules/anti-slop/src/effect/index.ts"
816
}
917
],
1018
"ignorePatterns": [
@@ -13,16 +21,44 @@
1321
"**/.next/**",
1422
"**/.open-next/**",
1523
"**/.wrangler/**",
24+
"**/.alchemy/**",
25+
"**/.output/**",
26+
"**/*.gen.ts",
1627
"**/tsconfig.json",
1728
"nub.lock",
1829
"packages/app/src/registry/**",
1930
"packages/common/src/db/migrations/**",
20-
"packages/app/cloudflare-env.d.ts",
21-
"packages/app/next-env.d.ts"
31+
"packages/app/cloudflare-env.d.ts"
2232
],
33+
"rules": {
34+
"anti-slop/no-chained-type-assertions": "warn",
35+
"anti-slop/no-conditional-empty-object-spread": "warn",
36+
"anti-slop/no-known-value-widening": "warn",
37+
"anti-slop/no-module-mocking": "warn",
38+
"anti-slop/no-object-parameters": "warn",
39+
"anti-slop/no-reflect-apply": "warn",
40+
"anti-slop/no-reflect-get": "warn",
41+
"anti-slop/no-runtime-typeof": "warn",
42+
"anti-slop/no-shape-in-symbol-names": "warn",
43+
"anti-slop/no-unknown-parameters": "warn",
44+
"anti-slop/no-unknown-returns": "warn",
45+
"anti-slop/no-unknown-type-aliases": "warn",
46+
"anti-slop/no-unsafe-dictionary-type": "warn",
47+
"anti-slop/no-widen-then-assert": "warn",
48+
"anti-slop/require-safety-comment-for-type-assertion": "warn",
49+
"anti-slop-effect/no-service-constructor-imports": "warn"
50+
},
2351
"overrides": [
2452
{
25-
"files": ["**/*.config.ts", "**/scripts/**", "src/index.ts"],
53+
"files": [
54+
"**/*.config.ts",
55+
"**/scripts/**",
56+
"src/index.ts",
57+
"tests/**",
58+
"packages/infra/src/alchemy.run.ts",
59+
"packages/infra/src/stack-options.ts",
60+
"packages/app/infra/index.ts"
61+
],
2662
"rules": {
2763
"effect/avoid-process-env": "off",
2864
"effect/avoid-node-imports": "off"

.tegami/2026-08-27-shared-project-dna.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@ packages:
1010
## Align the published toolchain with Shared Project DNA
1111

1212
Package consumers now get Nub 0.4.11, TypeScript 7.0.2, Effect 4.0.0-beta.107,
13-
Drizzle ORM 1.0.0-rc.5, and Zod 4.4.3. The CLI uses `effect/unstable/cli`
14-
instead of `@effect/cli`. `package.json` license fields match the AGPL-3.0
15-
`LICENSE` file.
13+
Drizzle ORM 1.0.0-rc.5, Zod 4.4.3, and Alchemy 2.0.0-beta.72. The dashboard is
14+
TanStack Start on Cloudflare instead of Next.js + OpenNext. The CLI stays on
15+
Effect 4 `effect/unstable/cli` because `@effect/cli` still peers Effect 3.
1616

17-
Install with `nub install`. The previous `pnpm` workspace and Biome
18-
lint/format path are gone. Alchemy stays on 0.43 until a later IaC rewrite.
17+
Install with `nub install`. The previous `pnpm` workspace, Biome lint/format
18+
path, and Next.js app entrypoints are gone.

nub.lock

Lines changed: 3422 additions & 3501 deletions
Large diffs are not rendered by default.

package.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@
3939
"cli": "tsx ./src/index.ts",
4040
"dev": "concurrently --kill-others --names \"API,APP\" --prefix-colors \"bgWhite,bgWhiteBright\" \"nub run dev:api\" \"wait-on http://localhost:8788 && nub run dev:app\"",
4141
"dev:app": "nub run --filter '@solstatus/app' dev",
42+
"dev:app:mock": "nub run --filter '@solstatus/app' dev:mock",
4243
"dev:api": "nub run --filter '@solstatus/api' dev",
4344
"db:setup": "nub run --filter '@solstatus/infra' db:setup",
4445
"deploy:prod": "nub run deploy:prod:api && nub run deploy:prod:app",
@@ -61,6 +62,7 @@
6162
"dependencies": {
6263
"@effect/platform-node": "4.0.0-beta.107",
6364
"@solstatus/infra": "workspace:*",
65+
"alchemy": "2.0.0-beta.72",
6466
"dotenv": "16.5.0",
6567
"effect": "4.0.0-beta.107"
6668
},
@@ -69,6 +71,7 @@
6971
"@mpsuesser/oxlint-plugin-effect": "0.2.2",
7072
"@oxlint/plugins": "1.79.0",
7173
"@types/node": "^24.12.4",
74+
"anti-slop": "github:dmmulroy/anti-slop",
7275
"concurrently": "^9.2.0",
7376
"oxfmt": "^0.49.0",
7477
"oxlint": "1.79.0",
@@ -81,6 +84,9 @@
8184
},
8285
"overrides": {
8386
"@effect/platform-node": "4.0.0-beta.107",
87+
"@types/react": "19.1.8",
88+
"@types/react-dom": "19.1.6",
89+
"alchemy": "2.0.0-beta.72",
8490
"drizzle-kit": "1.0.0-rc.5-ab785fc",
8591
"drizzle-orm": "1.0.0-rc.5-ab785fc",
8692
"effect": "4.0.0-beta.107",

packages/api/infra/index.ts

Lines changed: 56 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,89 +2,95 @@ import { dirname, resolve } from "node:path"
22
import { fileURLToPath } from "node:url"
33
import type { DBResource } from "@solstatus/common/infra"
44
import type { InfraMetadata } from "@solstatus/common/utils/types"
5-
import alchemy, { type } from "alchemy"
6-
import { DurableObjectNamespace, Worker } from "alchemy/cloudflare"
7-
import type { MonitorExec, MonitorTrigger, MonitorTriggerRPC } from "../src/index"
5+
import * as Cloudflare from "alchemy/Cloudflare"
6+
import * as Effect from "effect/Effect"
87

9-
export async function createApi(
8+
const __filename = fileURLToPath(import.meta.url)
9+
const __dirname = dirname(__filename)
10+
11+
export function createApi(
1012
resPrefix: string,
1113
stage: string,
1214
db: DBResource,
1315
cloudflareAccountId: string,
1416
) {
15-
const infraMetadata = {
16-
cloudflareAccountId: cloudflareAccountId,
17-
monitorExecName: `${resPrefix}-monitor-exec`,
18-
monitorTriggerName: `${resPrefix}-monitor-trigger`,
19-
}
20-
21-
const monitorExecWorker = await createMonitorExecWorker(infraMetadata, stage, db)
22-
const monitorTriggerWorker = await createMonitorTriggerWorker(
23-
infraMetadata,
24-
db,
25-
monitorExecWorker,
26-
)
27-
28-
return {
29-
monitorExecWorker,
30-
monitorTriggerWorker,
31-
}
17+
return Effect.gen(function* () {
18+
const infraMetadata = {
19+
cloudflareAccountId,
20+
monitorExecName: `${resPrefix}-monitor-exec`,
21+
monitorTriggerName: `${resPrefix}-monitor-trigger`,
22+
}
23+
const monitorExecWorker = yield* createMonitorExecWorker(infraMetadata, stage, db)
24+
const monitorTriggerWorker = yield* createMonitorTriggerWorker(
25+
infraMetadata,
26+
db,
27+
monitorExecWorker,
28+
)
29+
return {
30+
monitorExecWorker,
31+
monitorTriggerWorker,
32+
}
33+
})
3234
}
3335

34-
async function createMonitorExecWorker(
36+
export function createMonitorExecWorker(
3537
infraMetadata: InfraMetadata,
3638
stage: string,
3739
db: DBResource,
3840
) {
39-
const __filename = fileURLToPath(import.meta.url)
40-
const __dirname = dirname(__filename)
4141
const entrypoint = resolve(__dirname, "../src/monitor-exec.ts")
42-
43-
return await Worker(infraMetadata.monitorExecName, {
42+
return Cloudflare.Worker("monitor-exec", {
4443
name: infraMetadata.monitorExecName,
45-
adopt: true,
46-
entrypoint: entrypoint,
47-
rpc: type<MonitorExec>,
48-
bindings: {
44+
main: entrypoint,
45+
compatibility: {
46+
date: "2026-07-21",
47+
flags: ["nodejs_compat"],
48+
},
49+
observability: {
50+
enabled: true,
51+
},
52+
env: {
4953
DB: db,
50-
OPSGENIE_API_KEY: alchemy.secret(process.env.OPSGENIE_API_KEY || ""),
54+
OPSGENIE_API_KEY: process.env.OPSGENIE_API_KEY || "",
5155
APP_ENV: stage,
5256
MONITOR_EXEC_NAME: infraMetadata.monitorExecName,
5357
MONITOR_TRIGGER_NAME: infraMetadata.monitorTriggerName,
5458
CLOUDFLARE_ACCOUNT_ID: infraMetadata.cloudflareAccountId,
5559
},
5660
})
5761
}
58-
export type MonitorExecWorkerResource = Awaited<ReturnType<typeof createMonitorExecWorker>>
5962

60-
async function createMonitorTriggerWorker(
63+
export function createMonitorTriggerWorker(
6164
infraMetadata: InfraMetadata,
6265
db: DBResource,
63-
monitorExecWorker: MonitorExecWorkerResource,
66+
monitorExecWorker: Effect.Success<ReturnType<typeof createMonitorExecWorker>>,
6467
) {
65-
const __filename = fileURLToPath(import.meta.url)
66-
const __dirname = dirname(__filename)
6768
const entrypoint = resolve(__dirname, "../src/monitor-trigger.ts")
68-
69-
return await Worker(infraMetadata.monitorTriggerName, {
69+
const monitorTriggerDo = Cloudflare.DurableObject("monitor-trigger-do", {
70+
className: "MonitorTrigger",
71+
})
72+
return Cloudflare.Worker("monitor-trigger", {
7073
name: infraMetadata.monitorTriggerName,
71-
adopt: true,
72-
entrypoint: entrypoint,
73-
rpc: type<MonitorTriggerRPC>,
74-
bindings: {
74+
main: entrypoint,
75+
compatibility: {
76+
date: "2026-07-21",
77+
flags: ["nodejs_compat"],
78+
},
79+
observability: {
80+
enabled: true,
81+
},
82+
env: {
7583
DB: db,
7684
MONITOR_EXEC: monitorExecWorker,
77-
MONITOR_TRIGGER: new DurableObjectNamespace<MonitorTrigger>(
78-
`${infraMetadata.monitorTriggerName}-do`,
79-
{
80-
className: "MonitorTrigger",
81-
sqlite: true,
82-
},
83-
),
85+
MONITOR_TRIGGER: monitorTriggerDo,
8486
MONITOR_EXEC_NAME: infraMetadata.monitorExecName,
8587
MONITOR_TRIGGER_NAME: infraMetadata.monitorTriggerName,
8688
CLOUDFLARE_ACCOUNT_ID: infraMetadata.cloudflareAccountId,
8789
},
8890
})
8991
}
90-
export type MonitorTriggerWorkerResource = Awaited<ReturnType<typeof createMonitorTriggerWorker>>
92+
93+
export type MonitorExecWorkerResource = Effect.Success<ReturnType<typeof createMonitorExecWorker>>
94+
export type MonitorTriggerWorkerResource = Effect.Success<
95+
ReturnType<typeof createMonitorTriggerWorker>
96+
>

packages/api/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
"devDependencies": {
5656
"@cloudflare/workers-types": "^4.20250702.0",
5757
"@types/node": "^24.12.4",
58-
"alchemy": "^0.43.0",
58+
"alchemy": "2.0.0-beta.72",
5959
"concurrently": "^9.2.0",
6060
"drizzle-kit": "1.0.0-rc.5-ab785fc",
6161
"typescript": "7.0.2",

packages/app/cloudflare-env.d.ts

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
1-
declare namespace Cloudflare {
2-
type AppEnv = import("@/infra/types/env").AppEnv
3-
interface Env extends AppEnv {}
1+
import type { AppEnv } from "./infra/types/env"
2+
3+
declare global {
4+
namespace Cloudflare {
5+
interface Env extends AppEnv {}
6+
}
47
}
5-
interface CloudflareEnv extends Cloudflare.Env {}
8+
9+
export {}

packages/app/infra/index.ts

Lines changed: 32 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
import { dirname, resolve } from "node:path"
2+
import { fileURLToPath } from "node:url"
13
import type { MonitorExecWorkerResource, MonitorTriggerWorkerResource } from "@solstatus/api/infra"
24
import type { DBResource, SessionsStorageKVResource } from "@solstatus/common/infra"
3-
import alchemy from "alchemy"
4-
import { KVNamespace, Website } from "alchemy/cloudflare"
5+
import * as Cloudflare from "alchemy/Cloudflare"
56

6-
export async function createApp(
7+
const __filename = fileURLToPath(import.meta.url)
8+
const __dirname = dirname(__filename)
9+
const appRoot = resolve(__dirname, "..")
10+
11+
export function createApp(
712
resPrefix: string,
813
db: DBResource,
914
sessionsStorageKV: SessionsStorageKVResource,
@@ -13,49 +18,40 @@ export async function createApp(
1318
cloudflareAccountId: string,
1419
) {
1520
const appName = `${resPrefix}-app`
16-
17-
const kvIncrementalCache = await KVNamespace(`${appName}-inc-cache`, {
18-
title: `${appName}-inc-cache`,
19-
adopt: true,
20-
})
21-
22-
const app = await Website(appName, {
21+
return Cloudflare.Website.Vite("app", {
2322
name: appName,
24-
command: "nub run build:opennextjs",
25-
main: ".open-next/worker.js",
26-
assets: ".open-next/assets",
27-
url: !fqdn,
28-
compatibilityFlags: ["nodejs_compat"],
23+
rootDir: appRoot,
24+
compatibility: {
25+
date: "2026-07-21",
26+
flags: ["nodejs_compat"],
27+
},
2928
observability: {
3029
enabled: true,
3130
},
32-
wrangler: false,
33-
...(fqdn && {
34-
domains: [
35-
{
36-
domainName: fqdn,
37-
adopt: true,
38-
},
39-
],
40-
}),
41-
bindings: {
31+
dev: {
32+
port: 3000,
33+
strictPort: true,
34+
},
35+
...(fqdn
36+
? {
37+
domain: {
38+
name: fqdn,
39+
},
40+
workersDev: false,
41+
}
42+
: {}),
43+
env: {
4244
DB: db,
4345
SESSIONS_KV: sessionsStorageKV,
44-
BETTER_AUTH_SECRET: alchemy.secret(process.env.BETTER_AUTH_SECRET),
46+
BETTER_AUTH_SECRET: process.env.BETTER_AUTH_SECRET || "",
4547
MONITOR_EXEC: monitorExecWorker,
4648
MONITOR_TRIGGER_RPC: monitorTriggerWorker,
47-
NEXT_INC_CACHE_KV: kvIncrementalCache,
48-
MONITOR_EXEC_NAME: monitorExecWorker.name,
49-
MONITOR_TRIGGER_NAME: monitorTriggerWorker.name,
49+
MONITOR_EXEC_NAME: `${resPrefix}-monitor-exec`,
50+
MONITOR_TRIGGER_NAME: `${resPrefix}-monitor-trigger`,
5051
CLOUDFLARE_ACCOUNT_ID: cloudflareAccountId,
52+
VITE_APP_VERSION: process.env.VITE_APP_VERSION || "2.1.0-dev",
5153
},
5254
})
53-
54-
if (fqdn) {
55-
console.log(`${appName}: https://${fqdn}`)
56-
} else {
57-
console.log(`${appName}: ${app.url}`)
58-
}
59-
return app
6055
}
56+
6157
export type AppResource = Awaited<ReturnType<typeof createApp>>

0 commit comments

Comments
 (0)