ras-stack 0.30.1 → 0.32.0
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 +64 -534
- package/dist/build/cli.d.ts +1 -2
- package/dist/build/cli.js +8 -9
- package/dist/build/cli.js.map +1 -1
- package/dist/cli.d.ts +18 -0
- package/dist/cli.js +29 -0
- package/dist/cli.js.map +1 -0
- package/dist/conformance/index.d.ts +13 -0
- package/dist/conformance/index.js +43 -0
- package/dist/conformance/index.js.map +1 -1
- package/dist/policy/cli.d.ts +1 -2
- package/dist/policy/cli.js +18 -17
- package/dist/policy/cli.js.map +1 -1
- package/dist/posthog/client.d.ts +13 -0
- package/dist/posthog/client.js +31 -0
- package/dist/posthog/client.js.map +1 -0
- package/dist/posthog/config.d.ts +12 -0
- package/dist/posthog/config.js +49 -0
- package/dist/posthog/config.js.map +1 -0
- package/dist/posthog/coverage.d.ts +18 -0
- package/dist/posthog/coverage.js +25 -0
- package/dist/posthog/coverage.js.map +1 -0
- package/dist/posthog/index.d.ts +6 -0
- package/dist/posthog/index.js +4 -0
- package/dist/posthog/index.js.map +1 -0
- package/dist/posthog/proxy.d.ts +25 -0
- package/dist/posthog/proxy.js +25 -0
- package/dist/posthog/proxy.js.map +1 -0
- package/dist/posthog/react.d.ts +19 -0
- package/dist/posthog/react.js +18 -0
- package/dist/posthog/react.js.map +1 -0
- package/dist/posthog/request.d.ts +14 -0
- package/dist/posthog/request.js +22 -0
- package/dist/posthog/request.js.map +1 -0
- package/dist/posthog/server.d.ts +4 -0
- package/dist/posthog/server.js +17 -0
- package/dist/posthog/server.js.map +1 -0
- package/dist/preview/cli.d.ts +1 -2
- package/dist/preview/cli.js +5 -4
- package/dist/preview/cli.js.map +1 -1
- package/dist/runtime/dev-cli.d.ts +1 -2
- package/dist/runtime/dev-cli.js +8 -7
- package/dist/runtime/dev-cli.js.map +1 -1
- package/package.json +38 -6
package/README.md
CHANGED
|
@@ -1,580 +1,110 @@
|
|
|
1
|
-
|
|
1
|
+
<div align="center">
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# 🧱 ras-stack
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
I build several applications with the same TypeScript stack. They kept growing slightly different copies of the same code for session settings, origin checks, email delivery, realtime publication, resumable uploads, health checks, and project configuration.
|
|
8
|
-
|
|
9
|
-
`ras-stack` is the shared home for that plumbing. It is a personal library, published in case the code or the way it is split up is useful to someone else.
|
|
10
|
-
|
|
11
|
-
## What it is
|
|
12
|
-
|
|
13
|
-
The package contains independent helpers for common application infrastructure:
|
|
14
|
-
|
|
15
|
-
- **Authentication:** secure defaults and utilities for sessions, rate limits, secrets, social providers, tokens, and trusted origins.
|
|
16
|
-
- **Server requests:** same-origin mutation guards, error-normalizing RPC wrappers, canonical-host redirects, and health responses.
|
|
17
|
-
- **Realtime:** Centrifugo client lifecycle, token signing, presence synchronization, and a bounded publisher with retries and graceful shutdown.
|
|
18
|
-
- **Email:** SMTP environment parsing and a small Nodemailer delivery interface.
|
|
19
|
-
- **Database:** standard Better SQLite lifecycle and Drizzle migration mechanics that preserve the native typed database.
|
|
20
|
-
- **Uploads:** a promise-based wrapper around resumable `tus-js-client` uploads.
|
|
21
|
-
- **Project configuration:** shared TypeScript and Oxlint bases.
|
|
22
|
-
|
|
23
|
-
Each area has its own import path. An application can use one without adopting the rest.
|
|
24
|
-
|
|
25
|
-
## What it is not
|
|
26
|
-
|
|
27
|
-
This is not a framework, starter, application template, or complete authentication system. It does not own an application's database schema, migrations, routes, authorization rules, email templates, upload policy, or realtime event names.
|
|
28
|
-
|
|
29
|
-
The libraries underneath remain available normally. Applications still configure and call Better Auth, TanStack Start, Drizzle, Nodemailer, `tus-js-client`, and Centrifugo directly. The helpers only centralize the parts that would otherwise be copied unchanged.
|
|
30
|
-
|
|
31
|
-
## Install
|
|
32
|
-
|
|
33
|
-
`ras-stack` requires Node 24.
|
|
34
|
-
|
|
35
|
-
```sh
|
|
36
|
-
pnpm add ras-stack
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Nodemailer, Centrifuge, Better SQLite/Drizzle, and `tus-js-client` are optional peer dependencies. Install them only when using their integrations:
|
|
40
|
-
|
|
41
|
-
```sh
|
|
42
|
-
pnpm add nodemailer
|
|
43
|
-
pnpm add centrifuge
|
|
44
|
-
pnpm add tus-js-client
|
|
45
|
-
pnpm add better-sqlite3 drizzle-orm
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
## Authentication and request security
|
|
49
|
-
|
|
50
|
-
The auth entrypoint provides options and utilities rather than an auth factory. The application keeps its complete Better Auth configuration:
|
|
51
|
-
|
|
52
|
-
```ts
|
|
53
|
-
import { betterAuth } from 'better-auth'
|
|
54
|
-
import { configuredProviderOptions, standardRateLimitOptions, standardSessionOptions, trustedOrigins } from 'ras-stack/auth'
|
|
55
|
-
|
|
56
|
-
const auth = betterAuth({
|
|
57
|
-
database,
|
|
58
|
-
plugins,
|
|
59
|
-
socialProviders: configuredProviderOptions(['google', 'discord']),
|
|
60
|
-
session: standardSessionOptions(),
|
|
61
|
-
rateLimit: standardRateLimitOptions({ '/sign-up/email': { window: 60, max: 10 } }),
|
|
62
|
-
trustedOrigins: trustedOrigins({
|
|
63
|
-
configured: [process.env.APP_URL],
|
|
64
|
-
trustForwardedHeaders: true,
|
|
65
|
-
}),
|
|
66
|
-
})
|
|
67
|
-
```
|
|
68
|
-
|
|
69
|
-
The optional TanStack entrypoints bind the shared primitives to TanStack Start's ambient request and provide the common Query client default:
|
|
70
|
-
|
|
71
|
-
```ts
|
|
72
|
-
import { createStackQueryClient } from 'ras-stack/tanstack/query'
|
|
73
|
-
import {
|
|
74
|
-
betterAuthHandlers,
|
|
75
|
-
canonicalHostMiddleware,
|
|
76
|
-
createTanStackRpc,
|
|
77
|
-
requireTanStackMutationOrigin,
|
|
78
|
-
tanStackHealthHandler,
|
|
79
|
-
} from 'ras-stack/tanstack/server'
|
|
80
|
-
|
|
81
|
-
export const { rpc, mutationRpc } = createTanStackRpc({
|
|
82
|
-
requireMutation: (request) =>
|
|
83
|
-
requireTanStackMutationOrigin(
|
|
84
|
-
{
|
|
85
|
-
configured: [process.env.APP_URL],
|
|
86
|
-
trustForwardedHeaders: true,
|
|
87
|
-
},
|
|
88
|
-
request,
|
|
89
|
-
),
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
export const queryClient = createStackQueryClient()
|
|
93
|
-
|
|
94
|
-
export const authHandlers = betterAuthHandlers(() => app().auth)
|
|
95
|
-
export const healthHandler = tanStackHealthHandler(() => app().database.get(sql`SELECT 1`))
|
|
96
|
-
export const canonicalHost = canonicalHostMiddleware(() => ({ canonicalUrl: process.env.APP_URL }))
|
|
97
|
-
```
|
|
98
|
-
|
|
99
|
-
Applications still own their auth clients, authorization, file-route declarations, router, logging, health-check work, and Query configuration. Both integrations remain optional, and their upstream libraries remain directly accessible.
|
|
100
|
-
|
|
101
|
-
Only enable `trustForwardedHeaders` behind a proxy that replaces incoming forwarded headers. Otherwise a client could choose the origin used by the check.
|
|
102
|
-
|
|
103
|
-
Infrastructure boundaries can keep approved client output separate from private causes:
|
|
104
|
-
|
|
105
|
-
```ts
|
|
106
|
-
import { InfrastructureError, infrastructureDiagnostic, infrastructureFailure } from 'ras-stack/server'
|
|
107
|
-
|
|
108
|
-
throw new InfrastructureError('smtp_unavailable', 'email is temporarily unavailable', {
|
|
109
|
-
cause: transportError,
|
|
110
|
-
retryable: true,
|
|
111
|
-
})
|
|
112
|
-
|
|
113
|
-
const publicFailure = infrastructureFailure(error, {
|
|
114
|
-
code: 'internal_error',
|
|
115
|
-
message: 'something went wrong',
|
|
116
|
-
retryable: false,
|
|
117
|
-
})
|
|
118
|
-
logger.error(infrastructureDiagnostic(error), 'infrastructure request failed')
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
Only `InfrastructureError.publicMessage` is treated as approved for clients. Unknown failures use the caller's fallback; diagnostics are for application-owned logging and telemetry, never response serialization.
|
|
122
|
-
|
|
123
|
-
Browser auth flows can share failure classification and pending/error state without sharing forms or navigation:
|
|
124
|
-
|
|
125
|
-
```tsx
|
|
126
|
-
import { classifySignInFailure } from 'ras-stack/auth/client'
|
|
127
|
-
import { useAuthAction } from 'ras-stack/auth/react'
|
|
128
|
-
|
|
129
|
-
const signIn = useAuthAction({ failureMessage: (failure) => messageFor(classifySignInFailure(failure)) })
|
|
130
|
-
const result = await signIn.run(() => authClient.signIn.email({ email, password }))
|
|
131
|
-
if (!result.error) await navigateAfterSignIn()
|
|
132
|
-
```
|
|
133
|
-
|
|
134
|
-
Applications retain field models, validation, password-reset disclosure policy, two-factor transitions, telemetry, copy, and success navigation.
|
|
135
|
-
|
|
136
|
-
## Database lifecycle
|
|
137
|
-
|
|
138
|
-
The SQLite entrypoint owns the native client lifecycle, standard safety PRAGMAs, and optional Drizzle migrations while returning the upstream typed database:
|
|
139
|
-
|
|
140
|
-
```ts
|
|
141
|
-
import { bundledDirectory } from 'ras-stack/database'
|
|
142
|
-
import { openDrizzleSqlite } from 'ras-stack/database/sqlite'
|
|
143
|
-
|
|
144
|
-
const database = openDrizzleSqlite({
|
|
145
|
-
file,
|
|
146
|
-
schema,
|
|
147
|
-
migrationsFolder: bundledDirectory({
|
|
148
|
-
developmentUrl: new URL('../../drizzle', import.meta.url),
|
|
149
|
-
production: import.meta.env.PROD,
|
|
150
|
-
name: 'drizzle',
|
|
151
|
-
}),
|
|
152
|
-
})
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
Applications retain their schema, migrations, database path, repositories, transactions, and driver selection. Dual-database applications can use `openSqliteClient()` and `configureSqlite()` beneath their own wrapper without forcing PostgreSQL through a shared database interface.
|
|
156
|
-
|
|
157
|
-
The PostgreSQL entrypoint applies the same boundary to Postgres.js and Drizzle while returning both native objects:
|
|
158
|
-
|
|
159
|
-
```ts
|
|
160
|
-
import { databaseTarget } from 'ras-stack/database'
|
|
161
|
-
import { closeDrizzlePostgres, migrateDrizzlePostgres, openDrizzlePostgres } from 'ras-stack/database/postgres'
|
|
162
|
-
|
|
163
|
-
const target = databaseTarget({ databaseUrl: process.env.DATABASE_URL, sqliteFile })
|
|
164
|
-
if (target.provider === 'postgres') {
|
|
165
|
-
const connection = openDrizzlePostgres({ url: target.url, schema })
|
|
166
|
-
await migrateDrizzlePostgres(connection, postgresMigrationsFolder)
|
|
167
|
-
await closeDrizzlePostgres(connection)
|
|
168
|
-
}
|
|
169
|
-
```
|
|
170
|
-
|
|
171
|
-
The package does not make SQLite and PostgreSQL queries look identical. Applications keep driver-specific transaction and compatibility behavior while sharing target validation, pool defaults, numeric parsing, migrations, credential-safe display URLs, and shutdown.
|
|
172
|
-
|
|
173
|
-
Consumer tests can verify the real provider selection and provider-specific safety settings without sharing a schema or repository:
|
|
174
|
-
|
|
175
|
-
```ts
|
|
176
|
-
import { assertDatabaseTargetConformance, assertSqliteConformance } from 'ras-stack/conformance'
|
|
177
|
-
|
|
178
|
-
await assertDatabaseTargetConformance(databaseTarget)
|
|
179
|
-
await assertSqliteConformance((name) => sqliteClient.pragma(name, { simple: true }))
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
## Realtime updates
|
|
183
|
-
|
|
184
|
-
Applications choose their channel names, authorize subscriptions, and define payloads. `ras-stack` handles Centrifugo's HTTP publication, signed tokens, and repeated browser lifecycle mechanics:
|
|
185
|
-
|
|
186
|
-
```ts
|
|
187
|
-
import { CentrifugoPublisher, signRealtimeToken } from 'ras-stack/realtime'
|
|
188
|
-
import {
|
|
189
|
-
connectRealtimeClient,
|
|
190
|
-
createSameOriginRealtimeClient,
|
|
191
|
-
openRealtimeSubscription,
|
|
192
|
-
requestRealtimeTicket,
|
|
193
|
-
watchSubscriptionPresence,
|
|
194
|
-
} from 'ras-stack/realtime/client'
|
|
195
|
-
|
|
196
|
-
const publisher = new CentrifugoPublisher({
|
|
197
|
-
apiUrl,
|
|
198
|
-
apiKey,
|
|
199
|
-
maxConcurrentChannels: 8,
|
|
200
|
-
maxPendingChannels: 1024,
|
|
201
|
-
onError: (error, channel) => logger.error({ error, channel }, 'realtime publication failed'),
|
|
202
|
-
})
|
|
203
|
-
|
|
204
|
-
publisher.publish(`battle:${battle.id}`, { type: 'change' })
|
|
205
|
-
|
|
206
|
-
const token = signRealtimeToken(user.id, { channel: `battle:${battle.id}`, info: presence }, { secret })
|
|
207
|
-
|
|
208
|
-
await publisher.close()
|
|
209
|
-
|
|
210
|
-
const client = createSameOriginRealtimeClient({
|
|
211
|
-
getToken: () => requestRealtimeTicket('/api/realtime/token', { parse: (value) => (value as { token: string }).token }),
|
|
212
|
-
})
|
|
213
|
-
const channelToken = (channel: string) =>
|
|
214
|
-
requestRealtimeTicket('/api/realtime/token', {
|
|
215
|
-
init: { method: 'POST', body: JSON.stringify({ channel }) },
|
|
216
|
-
parse: (value) => (value as { token: string }).token,
|
|
217
|
-
})
|
|
218
|
-
const disconnect = connectRealtimeClient(client)
|
|
219
|
-
const live = openRealtimeSubscription(client, channel, { getToken: ({ channel }) => channelToken(channel) }, (subscription) =>
|
|
220
|
-
watchSubscriptionPresence(subscription, setClients),
|
|
221
|
-
)
|
|
222
|
-
|
|
223
|
-
live.close()
|
|
224
|
-
disconnect()
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
React applications can keep transport ownership equally small while retaining the native client and subscription:
|
|
228
|
-
|
|
229
|
-
```tsx
|
|
230
|
-
import { useCallback } from 'react'
|
|
231
|
-
import { useConnectedRealtimeClient, useRealtimePresence, useRealtimeSubscription } from 'ras-stack/realtime/react'
|
|
232
|
-
|
|
233
|
-
const createClient = useCallback(() => createSameOriginRealtimeClient({ getToken }), [workspaceId])
|
|
234
|
-
const client = useConnectedRealtimeClient(createClient)
|
|
235
|
-
const subscription = useRealtimeSubscription({ client, channel, options, configure })
|
|
236
|
-
const clients = useRealtimePresence(subscription)
|
|
237
|
-
```
|
|
238
|
-
|
|
239
|
-
The client factory may return a client or a promise, so applications can fetch and validate an initial ticket before connecting. Pass `onError` as the third argument to handle asynchronous ticket failures. A client that resolves after unmount is disconnected without ever connecting. The factory, error handler, subscription options, and configure callback should have stable identities and change only when their corresponding lifecycle should restart.
|
|
240
|
-
|
|
241
|
-
The client helpers return the underlying Centrifuge client and subscription. React ownership, channel conventions, ticket validation, event parsing, presence models, and query invalidation remain application code. `publish()` returns `false` when the publisher is closed, disabled, or at capacity. `close()` rejects new work and waits for accepted publications and their bounded retries to finish.
|
|
242
|
-
|
|
243
|
-
## Email and uploads
|
|
244
|
-
|
|
245
|
-
The optional integrations return the underlying library objects when an application needs more control:
|
|
246
|
-
|
|
247
|
-
```ts
|
|
248
|
-
import { createSmtpDelivery, createSmtpTransport, smtpConfigFromEnvironment } from 'ras-stack/email'
|
|
249
|
-
import { createTusUpload, startTusUpload } from 'ras-stack/uploads'
|
|
250
|
-
|
|
251
|
-
const smtp = smtpConfigFromEnvironment()
|
|
252
|
-
const email = smtp ? createSmtpDelivery(smtp) : undefined
|
|
253
|
-
|
|
254
|
-
const upload = createTusUpload({
|
|
255
|
-
endpoint: '/api/upload',
|
|
256
|
-
file,
|
|
257
|
-
metadata,
|
|
258
|
-
shouldRetry: (status) => status !== 423,
|
|
259
|
-
onProgress,
|
|
260
|
-
})
|
|
5
|
+
**A practical TypeScript stack with strong defaults and room to make it yours.**
|
|
261
6
|
|
|
262
|
-
|
|
263
|
-
```
|
|
7
|
+
TanStack Start · React · Better Auth · Drizzle · SQLite/PostgreSQL · Centrifugo · Caddy · PostHog
|
|
264
8
|
|
|
265
|
-
|
|
9
|
+
[](https://www.npmjs.com/package/ras-stack) [](https://github.com/richardsolomou/ras-stack/actions/workflows/ci.yml) [](LICENSE)
|
|
266
10
|
|
|
267
|
-
|
|
11
|
+
</div>
|
|
268
12
|
|
|
269
|
-
|
|
270
|
-
{
|
|
271
|
-
"outputDirectory": ".output/server",
|
|
272
|
-
"assets": [
|
|
273
|
-
{ "source": "drizzle", "destination": "drizzle" },
|
|
274
|
-
{ "source": "drizzle-postgres", "destination": "drizzle-postgres" }
|
|
275
|
-
]
|
|
276
|
-
}
|
|
277
|
-
```
|
|
13
|
+
I kept rebuilding the same boring parts: secure sessions, origin checks, database startup, realtime connections, process shutdown, CI, previews, and releases. `ras-stack` solves them once with the libraries I would choose anyway.
|
|
278
14
|
|
|
279
|
-
|
|
15
|
+
It is opinionated about security, lifecycle, failure handling, and supply-chain checks, but not product behavior. Use one helper or the whole stack, override what differs, and keep access to the library underneath.
|
|
280
16
|
|
|
281
|
-
|
|
17
|
+
## The idea 💡
|
|
282
18
|
|
|
283
|
-
|
|
284
|
-
import { clearGlobalSingleton, globalAsyncSingleton } from 'ras-stack/server'
|
|
19
|
+
TanStack handles the web application, Better Auth handles authentication, Drizzle handles typed data, and Centrifugo handles realtime delivery. `ras-stack` connects them; it does not replace them.
|
|
285
20
|
|
|
286
|
-
|
|
287
|
-
export const resetApp = () => clearGlobalSingleton('my-app.instance', (instance) => instance.close())
|
|
288
|
-
```
|
|
21
|
+
A helper belongs here when it removes a repeated decision or failure mode without hiding the underlying tool. Helpers return native objects, accept overrides, and live behind narrow entrypoints so applications can always drop down a level.
|
|
289
22
|
|
|
290
|
-
|
|
23
|
+
## What you get 📦
|
|
291
24
|
|
|
292
|
-
|
|
25
|
+
It ships in four forms:
|
|
293
26
|
|
|
294
|
-
|
|
27
|
+
- **TypeScript modules** under narrow import paths such as `ras-stack/database/sqlite`, `ras-stack/realtime/react`, and `ras-stack/tanstack/server`.
|
|
28
|
+
- **Command-line tools** for generated policy, production assets, preview status, and a local Centrifugo container.
|
|
29
|
+
- **GitHub Actions and reusable workflows** for toolchain setup, checks, browser tests, previews, and Changesets releases.
|
|
30
|
+
- **A separate OCI image** containing verified Caddy and Centrifugo binaries for production images.
|
|
295
31
|
|
|
296
|
-
|
|
297
|
-
{
|
|
298
|
-
"extends": ["./node_modules/ras-stack/config/oxlint.json"],
|
|
299
|
-
"rules": {
|
|
300
|
-
"application-specific-rule": "off"
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
```
|
|
32
|
+
An application can use one surface without adopting the others. The npm package has no runtime dependency on the web, database, email, or realtime libraries; those integrations are optional peers.
|
|
304
33
|
|
|
305
|
-
|
|
306
|
-
{
|
|
307
|
-
"extends": "ras-stack/config/typescript/tanstack",
|
|
308
|
-
"include": ["src", "vite.config.ts"]
|
|
309
|
-
}
|
|
310
|
-
```
|
|
311
|
-
|
|
312
|
-
TypeScript bases are also available at `ras-stack/config/typescript/browser` and `ras-stack/config/typescript/library`.
|
|
34
|
+
## The stack 🧰
|
|
313
35
|
|
|
314
|
-
|
|
36
|
+
These combinations are tested here and in production. [Sealed Lists](https://github.com/richardsolomou/sealed-lists), [Praetorium](https://github.com/richardsolomou/praetorium.gg), and [STL Quest](https://github.com/richardsolomou/stl.quest) use the application and runtime pieces. [BaseKit](https://github.com/richardsolomou/basekit) and [tro.gg](https://github.com/richardsolomou/tro.gg) use only the tooling that fits their different architectures.
|
|
315
37
|
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
38
|
+
| Layer | Supported technology | What `ras-stack` centralizes |
|
|
39
|
+
| ------------------- | ------------------------------------------- | ----------------------------------------------------------------------------------------------------- |
|
|
40
|
+
| Runtime and tooling | Node, ESM TypeScript, pnpm, Just, Oxlint | Compiler/linter bases, setup actions, and version synchronization |
|
|
41
|
+
| Web application | TanStack Start, React, TanStack Query | Request binding, mutation-origin checks, canonical hosts, health handlers, and Query defaults |
|
|
42
|
+
| Authentication | Better Auth | Secure option builders, origins, secrets, tokens, failure classification, and React action state |
|
|
43
|
+
| Data | Drizzle, `better-sqlite3`, Postgres.js | Connection lifecycle, safety defaults, migrations, target selection, and conformance checks |
|
|
44
|
+
| Realtime | Centrifuge, Centrifugo, Caddy | Publishing, tokens, browser/React lifecycle, presence, proxy configuration, binaries, and supervision |
|
|
45
|
+
| Email and uploads | Nodemailer, `tus-js-client` | SMTP configuration/delivery and promise-based resumable uploads |
|
|
46
|
+
| Observability | PostHog JS, React, and Node SDKs | Initialization, error defaults, request correlation, proxy routes, shutdown, and coverage decisions |
|
|
47
|
+
| Delivery | GitHub Actions, Changesets, Dokploy, Docker | Checks, releases, preview lifecycle/status, production assets, and runtime binaries |
|
|
322
48
|
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
```json
|
|
326
|
-
{
|
|
327
|
-
"extends": ["./node_modules/ras-stack/config/oxlint/application.json", "./node_modules/ras-stack/config/oxlint/tanstack.json"],
|
|
328
|
-
"rules": {
|
|
329
|
-
"application-specific-rule": "off"
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
```
|
|
49
|
+
Applications still configure every upstream library directly. This table describes what is tested together, not a replacement API.
|
|
333
50
|
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
## Repository policy
|
|
337
|
-
|
|
338
|
-
Policy files which cannot inherit can stay committed while being checked against the shared source. Select only the policies a repository wants in `ras-stack.policy.json`:
|
|
339
|
-
|
|
340
|
-
```json
|
|
341
|
-
{
|
|
342
|
-
"changesets": {
|
|
343
|
-
"overrides": {
|
|
344
|
-
"access": "restricted",
|
|
345
|
-
"privatePackages": { "version": true, "tag": true }
|
|
346
|
-
}
|
|
347
|
-
},
|
|
348
|
-
"dependabot": true,
|
|
349
|
-
"pnpm": {},
|
|
350
|
-
"adoption": {
|
|
351
|
-
"minimumRasStackVersion": "0.29.0",
|
|
352
|
-
"node": ">=24 <25",
|
|
353
|
-
"pnpm": "11.15.0",
|
|
354
|
-
"just": "1.58.0"
|
|
355
|
-
}
|
|
356
|
-
}
|
|
357
|
-
```
|
|
51
|
+
## Where it stops 🧭
|
|
358
52
|
|
|
359
|
-
|
|
53
|
+
`ras-stack` owns mechanics that should behave the same everywhere: safe database startup, mutation-origin checks, realtime tokens, process supervision, and preview status.
|
|
360
54
|
|
|
361
|
-
|
|
362
|
-
pnpm exec ras-stack-policy sync
|
|
363
|
-
pnpm exec ras-stack-policy check
|
|
364
|
-
pnpm exec ras-stack-policy sync adoption
|
|
365
|
-
pnpm exec ras-stack-policy check adoption
|
|
366
|
-
```
|
|
55
|
+
The application keeps schemas, migrations, repositories, routes, authorization, templates, upload rules, realtime payloads, storage, deployment topology, and UI. There is no shared application factory or giant configuration object.
|
|
367
56
|
|
|
368
|
-
|
|
57
|
+
The [`examples/full-stack`](examples/full-stack) workspace shows the boundaries together and tests them through `workspace:*`. It is an integration contract and reference, not a starter to copy.
|
|
369
58
|
|
|
370
|
-
|
|
59
|
+
## Pick what you need 🧩
|
|
371
60
|
|
|
372
|
-
|
|
61
|
+
`ras-stack` requires Node 24.
|
|
373
62
|
|
|
374
63
|
```sh
|
|
375
|
-
|
|
376
|
-
```
|
|
377
|
-
|
|
378
|
-
The command reads only `package.json`, root toolchain configs, and GitHub workflow metadata. It prints Markdown, exits unsuccessfully when drift exists, and never writes to a consumer repository. Omit an expectation when a repository intentionally does not share that surface. The included manually dispatched workflow writes the result to the Actions summary and retains it as an artifact.
|
|
379
|
-
|
|
380
|
-
## GitHub Actions
|
|
381
|
-
|
|
382
|
-
The JavaScript setup action reads the Node version from `engines.node` and the pnpm version from `packageManager` in the consuming repository:
|
|
383
|
-
|
|
384
|
-
```yaml
|
|
385
|
-
steps:
|
|
386
|
-
- uses: actions/checkout@v7
|
|
387
|
-
- uses: richardsolomou/ras-stack/actions/setup-js@v0.29.0
|
|
388
|
-
- run: pnpm check
|
|
389
|
-
```
|
|
390
|
-
|
|
391
|
-
Just is independent of the application language and is installed separately when a repository uses it:
|
|
392
|
-
|
|
393
|
-
```yaml
|
|
394
|
-
- uses: richardsolomou/ras-stack/actions/setup-just@v0.29.0
|
|
395
|
-
with:
|
|
396
|
-
version: '1.58.0'
|
|
397
|
-
```
|
|
398
|
-
|
|
399
|
-
Applications using Changesets can call the reusable release workflow after their own required checks:
|
|
400
|
-
|
|
401
|
-
```yaml
|
|
402
|
-
release:
|
|
403
|
-
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
|
|
404
|
-
needs: [check]
|
|
405
|
-
permissions:
|
|
406
|
-
contents: write
|
|
407
|
-
uses: richardsolomou/ras-stack/.github/workflows/release-changesets.yml@v0.29.0
|
|
408
|
-
secrets: inherit
|
|
409
|
-
```
|
|
410
|
-
|
|
411
|
-
Browser jobs can cache the pinned Playwright payload through the shared setup action. Production-container E2E can use the reusable workflow, while repository-specific preparation and the actual test command remain inputs:
|
|
412
|
-
|
|
413
|
-
```yaml
|
|
414
|
-
e2e:
|
|
415
|
-
uses: richardsolomou/ras-stack/.github/workflows/check-container-browser.yml@v0.29.0
|
|
416
|
-
with:
|
|
417
|
-
image: my-app-e2e
|
|
418
|
-
cache-scope: my-app-e2e
|
|
419
|
-
prepare-command: just prepare-e2e
|
|
420
|
-
command: just e2e-run
|
|
421
|
-
just-version: '1.58.0'
|
|
422
|
-
```
|
|
423
|
-
|
|
424
|
-
The loaded image tag is also available to the command as `RAS_STACK_TEST_IMAGE`. Build and browser durations are written to the job summary, and failure artifacts remain configurable. Applications that need extra caches, services, registry publication, or a different PR/main topology can use `actions/build-container` and `actions/setup-playwright` inside their own job instead.
|
|
425
|
-
|
|
426
|
-
Dokploy previews can share the application/domain/image/environment/deploy/health/delete/prune lifecycle through `ras-stack/preview/dokploy`. Product-specific Stripe, storage, seed, and verification work stays around the manager's configure and cleanup hooks.
|
|
427
|
-
|
|
428
|
-
Preview comments and commit checks can use the same state transition without carrying a GitHub API client in every repository:
|
|
429
|
-
|
|
430
|
-
```ts
|
|
431
|
-
import { reportPreviewStatus } from 'ras-stack/preview/github'
|
|
432
|
-
|
|
433
|
-
await reportPreviewStatus(
|
|
434
|
-
{ repository, token, marker: '<!-- app-preview -->', note: 'Preview data is disposable.' },
|
|
435
|
-
{ state: 'ready', prNumber, sha, previewUrl, runUrl },
|
|
436
|
-
)
|
|
437
|
-
```
|
|
438
|
-
|
|
439
|
-
The reporter keeps one marked comment and one named check run, preserves the last ready commit while a replacement builds, bounds comment pagination, and validates repository, pull request, commit, marker, and URL inputs. Applications retain their preview hostname, access note, seed credentials, and product cleanup hooks.
|
|
440
|
-
|
|
441
|
-
Trusted preview wrappers can delegate each status transition to the reusable workflow instead of checking out and running a repository-owned comment script:
|
|
442
|
-
|
|
443
|
-
```yaml
|
|
444
|
-
mark-preview-ready:
|
|
445
|
-
permissions:
|
|
446
|
-
contents: read
|
|
447
|
-
checks: write
|
|
448
|
-
issues: write
|
|
449
|
-
uses: richardsolomou/ras-stack/.github/workflows/report-preview-status.yml@v0.29.0
|
|
450
|
-
with:
|
|
451
|
-
state: ready
|
|
452
|
-
pr-number: ${{ github.event.workflow_run.pull_requests[0].number }}
|
|
453
|
-
sha: ${{ github.event.workflow_run.head_sha }}
|
|
454
|
-
preview-url: https://pr-${{ github.event.workflow_run.pull_requests[0].number }}.example.com
|
|
455
|
-
run-url: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
456
|
-
marker: <!-- app-preview -->
|
|
457
|
-
note: Preview data is disposable.
|
|
458
|
-
secrets:
|
|
459
|
-
token: ${{ secrets.GITHUB_TOKEN }}
|
|
460
|
-
```
|
|
461
|
-
|
|
462
|
-
The workflow uses the `ras-stack-preview` CLI from the consuming repository's pinned package. It owns only GitHub check/comment reporting; the caller retains the trusted event conditions, permissions, deployment, deletion, secret mapping, and product verification.
|
|
463
|
-
|
|
464
|
-
The reusable `build-preview-image.yml` workflow publishes same-repository pull requests directly but turns fork builds into one-day artifacts without exposing a token or secret. A trusted `workflow_run` job can publish that artifact with `actions/publish-preview-image` before running its repository-owned deployment command. The event wrapper and secret-to-environment mapping remain in each application so the trust boundary is visible locally.
|
|
465
|
-
|
|
466
|
-
Self-hosted images that run the app, Centrifugo, and Caddy together can share the lifecycle without sharing a Dockerfile:
|
|
467
|
-
|
|
468
|
-
```ts
|
|
469
|
-
import { runRealtimeStack } from 'ras-stack/runtime'
|
|
470
|
-
|
|
471
|
-
await runRealtimeStack({
|
|
472
|
-
app: { command: 'node', args: ['.output/server/index.mjs'], env: { ...process.env, PORT: '3001' } },
|
|
473
|
-
centrifugo: {
|
|
474
|
-
configPath: '/app/realtime.json',
|
|
475
|
-
env: process.env,
|
|
476
|
-
environment: realtime,
|
|
477
|
-
},
|
|
478
|
-
caddy: {
|
|
479
|
-
configPath: '/tmp/app/Caddyfile',
|
|
480
|
-
env: process.env,
|
|
481
|
-
},
|
|
482
|
-
})
|
|
483
|
-
```
|
|
484
|
-
|
|
485
|
-
`runRealtimeStack()` creates the Caddy configuration and supervises the standard app, Centrifugo, and Caddy topology. Any unexpected child exit stops its siblings; orchestrator signals receive a graceful window before remaining children are force-killed. Lower-level configuration and supervision functions remain available when a topology differs. Applications retain base images, namespaces, ports, volumes, secrets, per-process environment inheritance, preview seeding, and distributed-mode policy.
|
|
486
|
-
|
|
487
|
-
The separately released `ghcr.io/richardsolomou/ras-stack-runtime-binaries` image provides verified static Caddy and Centrifugo binaries without imposing an application base image. Copy the binaries from an immutable release and pin its digest:
|
|
488
|
-
|
|
489
|
-
```dockerfile
|
|
490
|
-
FROM ghcr.io/richardsolomou/ras-stack-runtime-binaries:runtime-v1.0.0@sha256:... AS runtime-binaries
|
|
491
|
-
COPY --from=runtime-binaries /usr/local/bin/caddy /usr/local/bin/caddy
|
|
492
|
-
COPY --from=runtime-binaries /usr/local/bin/centrifugo /usr/local/bin/centrifugo
|
|
64
|
+
pnpm add ras-stack
|
|
493
65
|
```
|
|
494
66
|
|
|
495
|
-
|
|
67
|
+
Nodemailer, Centrifuge, `better-sqlite3`, Drizzle, Postgres.js, and `tus-js-client` are optional peer dependencies. Install them only when using their integrations:
|
|
496
68
|
|
|
497
69
|
```sh
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
--secret development-secret
|
|
504
|
-
```
|
|
505
|
-
|
|
506
|
-
The foreground command follows terminal signals and leaves an existing named container alone. Add `--detach` to replace that named development container and return after startup. The host binding defaults to `127.0.0.1`; container-based callers that must reach Centrifugo through the Docker host can explicitly pass `--bind-address 0.0.0.0`. Applications with a Centrifugo connect proxy can pass its Docker-reachable URL through `--connect-proxy-endpoint`; channel definitions, proxy authorization, application environment, and Vite configuration remain in the application.
|
|
507
|
-
|
|
508
|
-
`runtime/VERSION` and `runtime/Dockerfile` own the release and source versions. Runtime tags publish independently from npm releases so binary changes must pass the full-stack production-container gate before a `runtime-v*` tag is created.
|
|
509
|
-
|
|
510
|
-
Read-only containers can pass writable `configHome` and `dataHome` paths to `caddyRuntimeEnvironment()`; both default to isolated directories under `/tmp`.
|
|
511
|
-
|
|
512
|
-
The workflow consumes pending changesets, commits the resulting versions and changelogs, pushes the commit and tag atomically, and creates a GitHub Release. It does nothing when no versioned changeset is present. The caller owns its checks, Changesets configuration, release policy, and any deployment that follows the release.
|
|
513
|
-
|
|
514
|
-
Pin actions and reusable workflows to a release tag and let Dependabot propose upgrades.
|
|
515
|
-
|
|
516
|
-
Reusable workflows cannot refer to an action at their own dynamic release tag. Their implementations therefore pin ras-stack actions to an older independently published bootstrap tag and advance that pin only when the action contract changes. Consumer examples and direct action calls should use the fleet baseline above.
|
|
517
|
-
|
|
518
|
-
The JavaScript setup action and shared check workflow reject Dependabot branches that do not contain the base commit recorded by the pull request event. Custom dependency workflows can apply the same guard directly:
|
|
519
|
-
|
|
520
|
-
```yaml
|
|
521
|
-
- uses: richardsolomou/ras-stack/actions/require-current-base@v0.29.0
|
|
522
|
-
if: github.event_name == 'pull_request' && startsWith(github.head_ref, 'dependabot/')
|
|
523
|
-
with:
|
|
524
|
-
base-sha: ${{ github.event.pull_request.base.sha }}
|
|
525
|
-
head-sha: ${{ github.event.pull_request.head.sha }}
|
|
70
|
+
pnpm add nodemailer
|
|
71
|
+
pnpm add centrifuge
|
|
72
|
+
pnpm add tus-js-client
|
|
73
|
+
pnpm add better-sqlite3 drizzle-orm
|
|
74
|
+
pnpm add postgres drizzle-orm
|
|
526
75
|
```
|
|
527
76
|
|
|
528
|
-
|
|
77
|
+
Start with the narrowest public entrypoint that owns the repeated mechanic:
|
|
529
78
|
|
|
530
|
-
|
|
79
|
+
| Need | Entrypoint or command | The application still owns |
|
|
80
|
+
| --------------------------------------------------------- | ------------------------------------------------------------------------------- | -------------------------------------------------------------- |
|
|
81
|
+
| Authentication defaults and browser action state | `ras-stack/auth`, `ras-stack/auth/client`, `ras-stack/auth/react` | Better Auth configuration, forms, policy, and navigation |
|
|
82
|
+
| RPC, mutation-origin, health, and canonical-host handling | `ras-stack/server`, `ras-stack/tanstack/server` | Routes, authorization, logging, and health work |
|
|
83
|
+
| SQLite or PostgreSQL lifecycle | `ras-stack/database/*` | Schemas, migrations, repositories, and transactions |
|
|
84
|
+
| Realtime publication and browser lifecycle | `ras-stack/realtime/*` | Channels, tickets, payloads, presence models, and invalidation |
|
|
85
|
+
| Email, uploads, and production assets | `ras-stack/email`, `ras-stack/uploads`, `ras assets` | Templates, metadata, quotas, storage, and asset contents |
|
|
86
|
+
| Production or development realtime runtime | `ras-stack/runtime`, `ras realtime` | Images, ports, secrets, volumes, and distributed policy |
|
|
87
|
+
| Compiler, lint, CI, release, and preview mechanics | `ras-stack/config/*`, `actions/*`, `.github/workflows/*`, `ras-stack/preview/*` | Triggers, permissions, services, deployment, and verification |
|
|
88
|
+
| Generated repository policy and adoption checks | `ras policy` | Which policies apply and every declared exception |
|
|
531
89
|
|
|
532
|
-
|
|
533
|
-
jobs:
|
|
534
|
-
check:
|
|
535
|
-
uses: richardsolomou/ras-stack/.github/workflows/check-js.yml@v0.29.0
|
|
536
|
-
with:
|
|
537
|
-
command: just check
|
|
538
|
-
just-version: '1.58.0'
|
|
539
|
-
```
|
|
540
|
-
|
|
541
|
-
Simple Playwright jobs can also share browser installation and failure artifacts:
|
|
542
|
-
|
|
543
|
-
```yaml
|
|
544
|
-
jobs:
|
|
545
|
-
end-to-end:
|
|
546
|
-
uses: richardsolomou/ras-stack/.github/workflows/check-browser.yml@v0.29.0
|
|
547
|
-
with:
|
|
548
|
-
prepare-command: pnpm build
|
|
549
|
-
command: pnpm test:e2e:run
|
|
550
|
-
artifact-path: test-results
|
|
551
|
-
```
|
|
90
|
+
## Guides 📚
|
|
552
91
|
|
|
553
|
-
|
|
92
|
+
| Guide | What it covers |
|
|
93
|
+
| -------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
94
|
+
| [Application primitives](docs/application-primitives.md) | Authentication, request security, databases, realtime clients, email, uploads, and stateful development resources |
|
|
95
|
+
| [Repository tooling](docs/repository-tooling.md) | TypeScript and Oxlint configuration, generated policy, fleet checks, GitHub Actions, previews, releases, and production runtime composition |
|
|
96
|
+
| [PostHog integration](docs/posthog.md) | Browser/server setup, identity and session correlation, ingest proxying, shutdown, coverage declarations, and source-map responsibility |
|
|
97
|
+
| [Full-stack example](docs/full-stack-example.md) | The `workspace:*` integration contract, local development, production container, and two-browser journey |
|
|
554
98
|
|
|
555
|
-
## Development
|
|
99
|
+
## Development 🛠️
|
|
556
100
|
|
|
557
|
-
Development requires Node 24
|
|
101
|
+
Development requires Node 24, pnpm 11.15.0, and Just 1.58.0.
|
|
558
102
|
|
|
559
103
|
```sh
|
|
560
104
|
just install
|
|
561
105
|
just check
|
|
562
106
|
```
|
|
563
107
|
|
|
564
|
-
### Full-stack example
|
|
565
|
-
|
|
566
|
-
`examples/full-stack` is a private workspace application whose `ras-stack` dependency is `workspace:*`. It imports only public package entrypoints, so the repository gate catches integration breakage before a release reaches consumers.
|
|
567
|
-
|
|
568
|
-
The example builds a real TanStack application and production image. Its browser journey signs two independent users in, writes through mutation-protected RPC into SQLite, uploads a bounded text file through TUS, and observes a cross-context message through Centrifugo and Caddy. The same app also consumes the shared query client, React realtime hooks, persisted auth secret, SMTP environment parsing, health response, singleton, TypeScript, and Oxlint contracts.
|
|
569
|
-
|
|
570
|
-
Run the compile and unit boundary with:
|
|
571
|
-
|
|
572
|
-
```sh
|
|
573
|
-
pnpm --filter @ras-stack/example-full-stack check
|
|
574
|
-
```
|
|
575
|
-
|
|
576
|
-
The CI `Full-stack example` job builds the container with a read-only root, starts the complete Node/Centrifugo/Caddy runtime, and runs the Playwright journey. Dokploy and GitHub preview reporting remain workflow integrations because exercising them requires repository credentials rather than application behavior.
|
|
577
|
-
|
|
578
108
|
See [CONTRIBUTING.md](CONTRIBUTING.md) for release instructions. Report vulnerabilities privately as described in [SECURITY.md](SECURITY.md).
|
|
579
109
|
|
|
580
110
|
## License
|
package/dist/build/cli.d.ts
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export {};
|
|
1
|
+
export declare function runAssetsCli(arguments_: string[]): Promise<void>;
|