ras-stack 2.0.1 → 3.0.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 +15 -42
- package/dist/changesets/cli.d.ts +1 -0
- package/dist/changesets/cli.js +14 -0
- package/dist/changesets/cli.js.map +1 -0
- package/dist/{policy/changesets.js → changesets/index.js} +1 -1
- package/dist/changesets/index.js.map +1 -0
- package/dist/cli.d.ts +2 -6
- package/dist/cli.js +3 -7
- package/dist/cli.js.map +1 -1
- package/dist/posthog/index.d.ts +0 -2
- package/dist/posthog/index.js +0 -1
- package/dist/posthog/index.js.map +1 -1
- package/dist/posthog/proxy.d.ts +11 -1
- package/dist/posthog/proxy.js +15 -3
- package/dist/posthog/proxy.js.map +1 -1
- package/package.json +12 -39
- package/config/oxlint/application.json +0 -16
- package/config/oxlint/domain.json +0 -29
- package/config/oxlint/layers.json +0 -116
- package/config/oxlint/tanstack.json +0 -4
- package/config/oxlint.json +0 -19
- package/config/typescript/base.json +0 -10
- package/config/typescript/browser.json +0 -8
- package/config/typescript/bundler.json +0 -9
- package/config/typescript/library.json +0 -11
- package/config/typescript/node-bundler.json +0 -9
- package/config/typescript/tanstack.json +0 -6
- package/dist/create/index.d.ts +0 -1
- package/dist/create/index.js +0 -83
- package/dist/create/index.js.map +0 -1
- package/dist/create/template/.env.example +0 -16
- package/dist/create/template/.oxfmtrc.json +0 -7
- package/dist/create/template/Dockerfile +0 -37
- package/dist/create/template/Dockerfile.standalone +0 -29
- package/dist/create/template/centrifugo.json +0 -14
- package/dist/create/template/dockerignore.template +0 -8
- package/dist/create/template/drizzle/0000_production_reference.sql +0 -114
- package/dist/create/template/drizzle/meta/_journal.json +0 -13
- package/dist/create/template/e2e/full-stack.spec.ts +0 -54
- package/dist/create/template/gitignore.template +0 -9
- package/dist/create/template/oxlint.json +0 -4
- package/dist/create/template/package.json +0 -60
- package/dist/create/template/playwright.config.ts +0 -7
- package/dist/create/template/pnpm-workspace.template.yaml +0 -13
- package/dist/create/template/ras-stack.assets.json +0 -4
- package/dist/create/template/scripts/containerRuntime.ts +0 -29
- package/dist/create/template/scripts/database.test.ts +0 -121
- package/dist/create/template/scripts/database.ts +0 -105
- package/dist/create/template/src/client/auth.ts +0 -3
- package/dist/create/template/src/client/queries.ts +0 -4
- package/dist/create/template/src/client/queryClient.ts +0 -1
- package/dist/create/template/src/client/useRealtime.ts +0 -20
- package/dist/create/template/src/posthog.ts +0 -21
- package/dist/create/template/src/routeTree.gen.ts +0 -230
- package/dist/create/template/src/router.tsx +0 -17
- package/dist/create/template/src/routes/__root.tsx +0 -40
- package/dist/create/template/src/routes/api/auth.$.ts +0 -8
- package/dist/create/template/src/routes/api/centrifugo.connect.ts +0 -29
- package/dist/create/template/src/routes/api/health.ts +0 -6
- package/dist/create/template/src/routes/api/live.ts +0 -5
- package/dist/create/template/src/routes/api/ready.ts +0 -30
- package/dist/create/template/src/routes/api/uploads.$id.ts +0 -40
- package/dist/create/template/src/routes/api/uploads.ts +0 -25
- package/dist/create/template/src/routes/index.tsx +0 -189
- package/dist/create/template/src/server/app.test.ts +0 -24
- package/dist/create/template/src/server/app.ts +0 -172
- package/dist/create/template/src/server/auth-flow.test.ts +0 -150
- package/dist/create/template/src/server/auth.ts +0 -56
- package/dist/create/template/src/server/environment.test.ts +0 -43
- package/dist/create/template/src/server/environment.ts +0 -67
- package/dist/create/template/src/server/fns.ts +0 -38
- package/dist/create/template/src/server/messages.test.ts +0 -38
- package/dist/create/template/src/server/messages.ts +0 -28
- package/dist/create/template/src/server/migration.test.ts +0 -34
- package/dist/create/template/src/server/outbox.test.ts +0 -130
- package/dist/create/template/src/server/outbox.ts +0 -110
- package/dist/create/template/src/server/posthog.test.ts +0 -34
- package/dist/create/template/src/server/rate-limit.test.ts +0 -32
- package/dist/create/template/src/server/rate-limit.ts +0 -29
- package/dist/create/template/src/server/rpc.ts +0 -16
- package/dist/create/template/src/server/schema.ts +0 -134
- package/dist/create/template/src/server/session.test.ts +0 -56
- package/dist/create/template/src/server/session.ts +0 -13
- package/dist/create/template/src/server/uploads.test.ts +0 -142
- package/dist/create/template/src/server/uploads.ts +0 -199
- package/dist/create/template/src/start.ts +0 -12
- package/dist/create/template/src/styles.css +0 -42
- package/dist/create/template/tsconfig.json +0 -8
- package/dist/create/template/vite.config.ts +0 -31
- package/dist/create/template/vitest.config.ts +0 -3
- package/dist/policy/changesets.js.map +0 -1
- package/dist/policy/cli.d.ts +0 -1
- package/dist/policy/cli.js +0 -23
- package/dist/policy/cli.js.map +0 -1
- package/dist/policy/index.d.ts +0 -14
- package/dist/policy/index.js +0 -132
- package/dist/policy/index.js.map +0 -1
- package/dist/policy/init-cli.d.ts +0 -12
- package/dist/policy/init-cli.js +0 -75
- package/dist/policy/init-cli.js.map +0 -1
- package/dist/policy/init.d.ts +0 -24
- package/dist/policy/init.js +0 -116
- package/dist/policy/init.js.map +0 -1
- package/dist/posthog/coverage.d.ts +0 -23
- package/dist/posthog/coverage.js +0 -30
- package/dist/posthog/coverage.js.map +0 -1
- package/dist/tanstack/query.d.ts +0 -3
- package/dist/tanstack/query.js +0 -8
- package/dist/tanstack/query.js.map +0 -1
- /package/dist/{policy/changesets.d.ts → changesets/index.d.ts} +0 -0
|
@@ -1,121 +0,0 @@
|
|
|
1
|
-
import Database from 'better-sqlite3'
|
|
2
|
-
import { mkdtemp, readdir, rm, writeFile } from 'node:fs/promises'
|
|
3
|
-
import os from 'node:os'
|
|
4
|
-
import path from 'node:path'
|
|
5
|
-
import { afterEach, beforeEach, expect, it, vi } from 'vitest'
|
|
6
|
-
import { runDatabaseCommand } from './database'
|
|
7
|
-
|
|
8
|
-
let directory: string
|
|
9
|
-
let source: string
|
|
10
|
-
|
|
11
|
-
beforeEach(async () => {
|
|
12
|
-
directory = await mkdtemp(path.join(os.tmpdir(), 'ras-stack-example-backup-'))
|
|
13
|
-
source = path.join(directory, 'example.sqlite')
|
|
14
|
-
writeValue(source, 'before')
|
|
15
|
-
})
|
|
16
|
-
|
|
17
|
-
afterEach(async () => {
|
|
18
|
-
await rm(directory, { recursive: true, force: true })
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
it('backs up and restores a validated database', async () => {
|
|
22
|
-
const backup = path.join(directory, 'backup.sqlite')
|
|
23
|
-
await runDatabaseCommand('backup', backup, { DATA_DIR: directory })
|
|
24
|
-
writeValue(source, 'after')
|
|
25
|
-
await runDatabaseCommand('restore', backup, { DATA_DIR: directory })
|
|
26
|
-
expect(readValue(source)).toBe('before')
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('rejects a corrupt restore candidate without changing the source', async () => {
|
|
30
|
-
const corrupt = path.join(directory, 'corrupt.sqlite')
|
|
31
|
-
await writeFile(corrupt, 'not sqlite')
|
|
32
|
-
await expect(runDatabaseCommand('restore', corrupt, { DATA_DIR: directory })).rejects.toThrow('file is not a database')
|
|
33
|
-
expect(readValue(source)).toBe('before')
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it('restores the original file and removes temporary state when replacement fails', async () => {
|
|
37
|
-
const backup = path.join(directory, 'backup.sqlite')
|
|
38
|
-
await runDatabaseCommand('backup', backup, { DATA_DIR: directory })
|
|
39
|
-
writeValue(source, 'current')
|
|
40
|
-
const operations = await import('node:fs/promises')
|
|
41
|
-
let renames = 0
|
|
42
|
-
const rename = vi.fn(async (from: string, to: string) => {
|
|
43
|
-
renames += 1
|
|
44
|
-
if (renames === 2) throw new Error('replacement failed')
|
|
45
|
-
await operations.rename(from, to)
|
|
46
|
-
}) as typeof operations.rename
|
|
47
|
-
await expect(runDatabaseCommand('restore', backup, { DATA_DIR: directory }, { ...operations, rename })).rejects.toThrow(
|
|
48
|
-
'replacement failed',
|
|
49
|
-
)
|
|
50
|
-
expect(readValue(source)).toBe('current')
|
|
51
|
-
expect((await readdir(directory)).some((file) => file.includes('.restore-'))).toBe(false)
|
|
52
|
-
})
|
|
53
|
-
|
|
54
|
-
it('restores the original file even when temporary cleanup also fails', async () => {
|
|
55
|
-
const backup = path.join(directory, 'backup.sqlite')
|
|
56
|
-
await runDatabaseCommand('backup', backup, { DATA_DIR: directory })
|
|
57
|
-
writeValue(source, 'current')
|
|
58
|
-
const operations = await import('node:fs/promises')
|
|
59
|
-
let renames = 0
|
|
60
|
-
const rename = vi.fn(async (from: string, to: string) => {
|
|
61
|
-
renames += 1
|
|
62
|
-
if (renames === 2) throw new Error('replacement failed')
|
|
63
|
-
await operations.rename(from, to)
|
|
64
|
-
}) as typeof operations.rename
|
|
65
|
-
const failure = await runDatabaseCommand(
|
|
66
|
-
'restore',
|
|
67
|
-
backup,
|
|
68
|
-
{ DATA_DIR: directory },
|
|
69
|
-
{
|
|
70
|
-
...operations,
|
|
71
|
-
rename,
|
|
72
|
-
rm: vi.fn().mockRejectedValue(new Error('cleanup failed')),
|
|
73
|
-
},
|
|
74
|
-
).catch((error: unknown) => error)
|
|
75
|
-
expect(failure).toBeInstanceOf(AggregateError)
|
|
76
|
-
expect((failure as AggregateError).errors.map(String)).toEqual(['Error: replacement failed', 'Error: cleanup failed'])
|
|
77
|
-
expect(readValue(source)).toBe('current')
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
it('reports rollback and cleanup failures after a replacement failure', async () => {
|
|
81
|
-
const backup = path.join(directory, 'backup.sqlite')
|
|
82
|
-
await runDatabaseCommand('backup', backup, { DATA_DIR: directory })
|
|
83
|
-
const operations = await import('node:fs/promises')
|
|
84
|
-
let renames = 0
|
|
85
|
-
const rename = vi.fn(async (from: string, to: string) => {
|
|
86
|
-
renames += 1
|
|
87
|
-
if (renames === 2) throw new Error('replacement failed')
|
|
88
|
-
if (renames === 3) throw new Error('rollback failed')
|
|
89
|
-
await operations.rename(from, to)
|
|
90
|
-
}) as typeof operations.rename
|
|
91
|
-
const failure = await runDatabaseCommand(
|
|
92
|
-
'restore',
|
|
93
|
-
backup,
|
|
94
|
-
{ DATA_DIR: directory },
|
|
95
|
-
{
|
|
96
|
-
...operations,
|
|
97
|
-
rename,
|
|
98
|
-
rm: vi.fn().mockRejectedValue(new Error('cleanup failed')),
|
|
99
|
-
},
|
|
100
|
-
).catch((error: unknown) => error)
|
|
101
|
-
expect((failure as AggregateError).errors.map(String)).toEqual([
|
|
102
|
-
'Error: replacement failed',
|
|
103
|
-
'Error: rollback failed',
|
|
104
|
-
'Error: cleanup failed',
|
|
105
|
-
])
|
|
106
|
-
expect(rename).toHaveBeenCalledTimes(3)
|
|
107
|
-
})
|
|
108
|
-
|
|
109
|
-
function writeValue(file: string, value: string) {
|
|
110
|
-
const database = new Database(file)
|
|
111
|
-
database.exec('DROP TABLE IF EXISTS proof; CREATE TABLE proof (value TEXT NOT NULL)')
|
|
112
|
-
database.prepare('INSERT INTO proof (value) VALUES (?)').run(value)
|
|
113
|
-
database.close()
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function readValue(file: string) {
|
|
117
|
-
const database = new Database(file, { readonly: true })
|
|
118
|
-
const value = database.prepare('SELECT value FROM proof').pluck().get() as string
|
|
119
|
-
database.close()
|
|
120
|
-
return value
|
|
121
|
-
}
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import Database from 'better-sqlite3'
|
|
2
|
-
import { existsSync } from 'node:fs'
|
|
3
|
-
import { copyFile, mkdir, rename, rm } from 'node:fs/promises'
|
|
4
|
-
import path from 'node:path'
|
|
5
|
-
import { fileURLToPath } from 'node:url'
|
|
6
|
-
|
|
7
|
-
type FileOperations = { copyFile: typeof copyFile; mkdir: typeof mkdir; rename: typeof rename; rm: typeof rm }
|
|
8
|
-
const defaultFileOperations: FileOperations = { copyFile, mkdir, rename, rm }
|
|
9
|
-
|
|
10
|
-
export async function runDatabaseCommand(
|
|
11
|
-
command: 'backup' | 'restore',
|
|
12
|
-
suppliedPath: string | undefined,
|
|
13
|
-
environment: NodeJS.ProcessEnv = process.env,
|
|
14
|
-
fileOperations: FileOperations = defaultFileOperations,
|
|
15
|
-
) {
|
|
16
|
-
const dataDirectory = path.resolve(environment.DATA_DIR ?? '.data/example-full-stack')
|
|
17
|
-
const source = path.join(dataDirectory, 'example.sqlite')
|
|
18
|
-
if (command === 'backup') {
|
|
19
|
-
const backupDirectory = path.join(dataDirectory, 'backups')
|
|
20
|
-
await fileOperations.mkdir(backupDirectory, { recursive: true })
|
|
21
|
-
const destination = suppliedPath
|
|
22
|
-
? path.resolve(suppliedPath)
|
|
23
|
-
: path.join(backupDirectory, `example-${new Date().toISOString().replaceAll(':', '-')}.sqlite`)
|
|
24
|
-
const database = new Database(source, { readonly: true, fileMustExist: true })
|
|
25
|
-
try {
|
|
26
|
-
integrity(database, 'source')
|
|
27
|
-
await database.backup(destination)
|
|
28
|
-
const copy = new Database(destination, { readonly: true, fileMustExist: true })
|
|
29
|
-
try {
|
|
30
|
-
integrity(copy, 'backup')
|
|
31
|
-
} finally {
|
|
32
|
-
copy.close()
|
|
33
|
-
}
|
|
34
|
-
return destination
|
|
35
|
-
} finally {
|
|
36
|
-
database.close()
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
if (!suppliedPath) throw new Error('restore requires a backup file')
|
|
41
|
-
const candidate = path.resolve(suppliedPath)
|
|
42
|
-
const backup = new Database(candidate, { readonly: true, fileMustExist: true })
|
|
43
|
-
try {
|
|
44
|
-
integrity(backup, 'backup')
|
|
45
|
-
} finally {
|
|
46
|
-
backup.close()
|
|
47
|
-
}
|
|
48
|
-
await fileOperations.mkdir(dataDirectory, { recursive: true })
|
|
49
|
-
for (const sidecar of [`${source}-wal`, `${source}-shm`]) {
|
|
50
|
-
if (existsSync(sidecar)) throw new Error(`Refusing to restore while SQLite sidecar exists: ${sidecar}`)
|
|
51
|
-
}
|
|
52
|
-
const temporary = `${source}.restore-${process.pid}`
|
|
53
|
-
await fileOperations.copyFile(candidate, temporary)
|
|
54
|
-
const restored = new Database(temporary, { readonly: true, fileMustExist: true })
|
|
55
|
-
try {
|
|
56
|
-
integrity(restored, 'restored')
|
|
57
|
-
} finally {
|
|
58
|
-
restored.close()
|
|
59
|
-
}
|
|
60
|
-
const previous = `${source}.before-restore-${Date.now()}`
|
|
61
|
-
const hasPrevious = existsSync(source)
|
|
62
|
-
if (hasPrevious) await fileOperations.rename(source, previous)
|
|
63
|
-
try {
|
|
64
|
-
await fileOperations.rename(temporary, source)
|
|
65
|
-
} catch (error) {
|
|
66
|
-
const failures = [error]
|
|
67
|
-
if (hasPrevious) {
|
|
68
|
-
try {
|
|
69
|
-
await fileOperations.rename(previous, source)
|
|
70
|
-
} catch (rollbackError) {
|
|
71
|
-
failures.push(rollbackError)
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
try {
|
|
75
|
-
await fileOperations.rm(temporary, { force: true })
|
|
76
|
-
} catch (cleanupError) {
|
|
77
|
-
failures.push(cleanupError)
|
|
78
|
-
}
|
|
79
|
-
if (failures.length === 1) throw error
|
|
80
|
-
const failure = new AggregateError(failures, 'Restore replacement failed and recovery was incomplete')
|
|
81
|
-
failure.cause = error
|
|
82
|
-
throw failure
|
|
83
|
-
}
|
|
84
|
-
return source
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
function integrity(database: Database.Database, label: string) {
|
|
88
|
-
const result = database.pragma('quick_check', { simple: true })
|
|
89
|
-
if (result !== 'ok') throw new Error(`${label} database failed quick_check: ${String(result)}`)
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
93
|
-
const [command, suppliedPath] = process.argv.slice(2)
|
|
94
|
-
if (command !== 'backup' && command !== 'restore') {
|
|
95
|
-
console.error('usage: database.mjs <backup [destination]|restore backup-file>')
|
|
96
|
-
process.exitCode = 2
|
|
97
|
-
} else {
|
|
98
|
-
try {
|
|
99
|
-
console.log(await runDatabaseCommand(command, suppliedPath))
|
|
100
|
-
} catch (error) {
|
|
101
|
-
console.error(error)
|
|
102
|
-
process.exitCode = 1
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export { createStackQueryClient as createQueryClient, queryErrorMessage } from 'ras-stack/tanstack/query'
|
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
import { useQueryClient } from '@tanstack/react-query'
|
|
2
|
-
import { createSameOriginRealtimeClient } from 'ras-stack/realtime/client'
|
|
3
|
-
import { useConnectedRealtimeClient, useRealtimeSubscription } from 'ras-stack/realtime/react'
|
|
4
|
-
import { useCallback } from 'react'
|
|
5
|
-
import { snapshotQuery } from './queries'
|
|
6
|
-
|
|
7
|
-
export function useRealtime(enabled: boolean) {
|
|
8
|
-
const queryClient = useQueryClient()
|
|
9
|
-
const create = useCallback(() => createSameOriginRealtimeClient({}), [])
|
|
10
|
-
const client = useConnectedRealtimeClient(create, enabled)
|
|
11
|
-
const configure = useCallback(
|
|
12
|
-
(subscription: NonNullable<ReturnType<typeof useRealtimeSubscription>>) => {
|
|
13
|
-
const refresh = () => void queryClient.invalidateQueries({ queryKey: snapshotQuery().queryKey })
|
|
14
|
-
subscription.on('publication', refresh)
|
|
15
|
-
return () => subscription.off('publication', refresh)
|
|
16
|
-
},
|
|
17
|
-
[queryClient],
|
|
18
|
-
)
|
|
19
|
-
useRealtimeSubscription({ client, channel: 'messages:all', enabled, configure })
|
|
20
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import { definePostHogCoverage } from 'ras-stack/posthog'
|
|
2
|
-
|
|
3
|
-
export const postHogCoverage = definePostHogCoverage({
|
|
4
|
-
browser: {
|
|
5
|
-
analytics: true,
|
|
6
|
-
errorTracking: true,
|
|
7
|
-
identity: true,
|
|
8
|
-
logs: true,
|
|
9
|
-
metrics: true,
|
|
10
|
-
sessionReplay: { disabled: 'The example does not handle user data worth replaying' },
|
|
11
|
-
featureFlags: { disabled: 'The example has no rollout-controlled behavior' },
|
|
12
|
-
},
|
|
13
|
-
server: {
|
|
14
|
-
analytics: true,
|
|
15
|
-
errorTracking: true,
|
|
16
|
-
logs: true,
|
|
17
|
-
metrics: true,
|
|
18
|
-
tracing: true,
|
|
19
|
-
},
|
|
20
|
-
sourceMaps: { disabled: 'The example is not deployed as a user-facing application' },
|
|
21
|
-
})
|
|
@@ -1,230 +0,0 @@
|
|
|
1
|
-
/* eslint-disable */
|
|
2
|
-
|
|
3
|
-
// @ts-nocheck
|
|
4
|
-
|
|
5
|
-
// noinspection JSUnusedGlobalSymbols
|
|
6
|
-
|
|
7
|
-
// This file was automatically generated by TanStack Router.
|
|
8
|
-
// You should NOT make any changes in this file as it will be overwritten.
|
|
9
|
-
// Additionally, you should also exclude this file from your linter and/or formatter to prevent it from being checked or modified.
|
|
10
|
-
|
|
11
|
-
import { Route as rootRouteImport } from './routes/__root'
|
|
12
|
-
import { Route as IndexRouteImport } from './routes/index'
|
|
13
|
-
import { Route as ApiHealthRouteImport } from './routes/api/health'
|
|
14
|
-
import { Route as ApiLiveRouteImport } from './routes/api/live'
|
|
15
|
-
import { Route as ApiReadyRouteImport } from './routes/api/ready'
|
|
16
|
-
import { Route as ApiUploadsRouteImport } from './routes/api/uploads'
|
|
17
|
-
import { Route as ApiAuthSplatRouteImport } from './routes/api/auth.$'
|
|
18
|
-
import { Route as ApiCentrifugoConnectRouteImport } from './routes/api/centrifugo.connect'
|
|
19
|
-
import { Route as ApiUploadsIdRouteImport } from './routes/api/uploads.$id'
|
|
20
|
-
|
|
21
|
-
const IndexRoute = IndexRouteImport.update({
|
|
22
|
-
id: '/',
|
|
23
|
-
path: '/',
|
|
24
|
-
getParentRoute: () => rootRouteImport,
|
|
25
|
-
} as any)
|
|
26
|
-
const ApiHealthRoute = ApiHealthRouteImport.update({
|
|
27
|
-
id: '/api/health',
|
|
28
|
-
path: '/api/health',
|
|
29
|
-
getParentRoute: () => rootRouteImport,
|
|
30
|
-
} as any)
|
|
31
|
-
const ApiLiveRoute = ApiLiveRouteImport.update({
|
|
32
|
-
id: '/api/live',
|
|
33
|
-
path: '/api/live',
|
|
34
|
-
getParentRoute: () => rootRouteImport,
|
|
35
|
-
} as any)
|
|
36
|
-
const ApiReadyRoute = ApiReadyRouteImport.update({
|
|
37
|
-
id: '/api/ready',
|
|
38
|
-
path: '/api/ready',
|
|
39
|
-
getParentRoute: () => rootRouteImport,
|
|
40
|
-
} as any)
|
|
41
|
-
const ApiUploadsRoute = ApiUploadsRouteImport.update({
|
|
42
|
-
id: '/api/uploads',
|
|
43
|
-
path: '/api/uploads',
|
|
44
|
-
getParentRoute: () => rootRouteImport,
|
|
45
|
-
} as any)
|
|
46
|
-
const ApiAuthSplatRoute = ApiAuthSplatRouteImport.update({
|
|
47
|
-
id: '/api/auth/$',
|
|
48
|
-
path: '/api/auth/$',
|
|
49
|
-
getParentRoute: () => rootRouteImport,
|
|
50
|
-
} as any)
|
|
51
|
-
const ApiCentrifugoConnectRoute = ApiCentrifugoConnectRouteImport.update({
|
|
52
|
-
id: '/api/centrifugo/connect',
|
|
53
|
-
path: '/api/centrifugo/connect',
|
|
54
|
-
getParentRoute: () => rootRouteImport,
|
|
55
|
-
} as any)
|
|
56
|
-
const ApiUploadsIdRoute = ApiUploadsIdRouteImport.update({
|
|
57
|
-
id: '/$id',
|
|
58
|
-
path: '/$id',
|
|
59
|
-
getParentRoute: () => ApiUploadsRoute,
|
|
60
|
-
} as any)
|
|
61
|
-
|
|
62
|
-
export interface FileRoutesByFullPath {
|
|
63
|
-
'/': typeof IndexRoute
|
|
64
|
-
'/api/health': typeof ApiHealthRoute
|
|
65
|
-
'/api/live': typeof ApiLiveRoute
|
|
66
|
-
'/api/ready': typeof ApiReadyRoute
|
|
67
|
-
'/api/uploads': typeof ApiUploadsRouteWithChildren
|
|
68
|
-
'/api/auth/$': typeof ApiAuthSplatRoute
|
|
69
|
-
'/api/centrifugo/connect': typeof ApiCentrifugoConnectRoute
|
|
70
|
-
'/api/uploads/$id': typeof ApiUploadsIdRoute
|
|
71
|
-
}
|
|
72
|
-
export interface FileRoutesByTo {
|
|
73
|
-
'/': typeof IndexRoute
|
|
74
|
-
'/api/health': typeof ApiHealthRoute
|
|
75
|
-
'/api/live': typeof ApiLiveRoute
|
|
76
|
-
'/api/ready': typeof ApiReadyRoute
|
|
77
|
-
'/api/uploads': typeof ApiUploadsRouteWithChildren
|
|
78
|
-
'/api/auth/$': typeof ApiAuthSplatRoute
|
|
79
|
-
'/api/centrifugo/connect': typeof ApiCentrifugoConnectRoute
|
|
80
|
-
'/api/uploads/$id': typeof ApiUploadsIdRoute
|
|
81
|
-
}
|
|
82
|
-
export interface FileRoutesById {
|
|
83
|
-
__root__: typeof rootRouteImport
|
|
84
|
-
'/': typeof IndexRoute
|
|
85
|
-
'/api/health': typeof ApiHealthRoute
|
|
86
|
-
'/api/live': typeof ApiLiveRoute
|
|
87
|
-
'/api/ready': typeof ApiReadyRoute
|
|
88
|
-
'/api/uploads': typeof ApiUploadsRouteWithChildren
|
|
89
|
-
'/api/auth/$': typeof ApiAuthSplatRoute
|
|
90
|
-
'/api/centrifugo/connect': typeof ApiCentrifugoConnectRoute
|
|
91
|
-
'/api/uploads/$id': typeof ApiUploadsIdRoute
|
|
92
|
-
}
|
|
93
|
-
export interface FileRouteTypes {
|
|
94
|
-
fileRoutesByFullPath: FileRoutesByFullPath
|
|
95
|
-
fullPaths:
|
|
96
|
-
| '/'
|
|
97
|
-
| '/api/health'
|
|
98
|
-
| '/api/live'
|
|
99
|
-
| '/api/ready'
|
|
100
|
-
| '/api/uploads'
|
|
101
|
-
| '/api/auth/$'
|
|
102
|
-
| '/api/centrifugo/connect'
|
|
103
|
-
| '/api/uploads/$id'
|
|
104
|
-
fileRoutesByTo: FileRoutesByTo
|
|
105
|
-
to:
|
|
106
|
-
| '/'
|
|
107
|
-
| '/api/health'
|
|
108
|
-
| '/api/live'
|
|
109
|
-
| '/api/ready'
|
|
110
|
-
| '/api/uploads'
|
|
111
|
-
| '/api/auth/$'
|
|
112
|
-
| '/api/centrifugo/connect'
|
|
113
|
-
| '/api/uploads/$id'
|
|
114
|
-
id:
|
|
115
|
-
| '__root__'
|
|
116
|
-
| '/'
|
|
117
|
-
| '/api/health'
|
|
118
|
-
| '/api/live'
|
|
119
|
-
| '/api/ready'
|
|
120
|
-
| '/api/uploads'
|
|
121
|
-
| '/api/auth/$'
|
|
122
|
-
| '/api/centrifugo/connect'
|
|
123
|
-
| '/api/uploads/$id'
|
|
124
|
-
fileRoutesById: FileRoutesById
|
|
125
|
-
}
|
|
126
|
-
export interface RootRouteChildren {
|
|
127
|
-
IndexRoute: typeof IndexRoute
|
|
128
|
-
ApiHealthRoute: typeof ApiHealthRoute
|
|
129
|
-
ApiLiveRoute: typeof ApiLiveRoute
|
|
130
|
-
ApiReadyRoute: typeof ApiReadyRoute
|
|
131
|
-
ApiUploadsRoute: typeof ApiUploadsRouteWithChildren
|
|
132
|
-
ApiAuthSplatRoute: typeof ApiAuthSplatRoute
|
|
133
|
-
ApiCentrifugoConnectRoute: typeof ApiCentrifugoConnectRoute
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
declare module '@tanstack/react-router' {
|
|
137
|
-
interface FileRoutesByPath {
|
|
138
|
-
'/': {
|
|
139
|
-
id: '/'
|
|
140
|
-
path: '/'
|
|
141
|
-
fullPath: '/'
|
|
142
|
-
preLoaderRoute: typeof IndexRouteImport
|
|
143
|
-
parentRoute: typeof rootRouteImport
|
|
144
|
-
}
|
|
145
|
-
'/api/health': {
|
|
146
|
-
id: '/api/health'
|
|
147
|
-
path: '/api/health'
|
|
148
|
-
fullPath: '/api/health'
|
|
149
|
-
preLoaderRoute: typeof ApiHealthRouteImport
|
|
150
|
-
parentRoute: typeof rootRouteImport
|
|
151
|
-
}
|
|
152
|
-
'/api/live': {
|
|
153
|
-
id: '/api/live'
|
|
154
|
-
path: '/api/live'
|
|
155
|
-
fullPath: '/api/live'
|
|
156
|
-
preLoaderRoute: typeof ApiLiveRouteImport
|
|
157
|
-
parentRoute: typeof rootRouteImport
|
|
158
|
-
}
|
|
159
|
-
'/api/ready': {
|
|
160
|
-
id: '/api/ready'
|
|
161
|
-
path: '/api/ready'
|
|
162
|
-
fullPath: '/api/ready'
|
|
163
|
-
preLoaderRoute: typeof ApiReadyRouteImport
|
|
164
|
-
parentRoute: typeof rootRouteImport
|
|
165
|
-
}
|
|
166
|
-
'/api/uploads': {
|
|
167
|
-
id: '/api/uploads'
|
|
168
|
-
path: '/api/uploads'
|
|
169
|
-
fullPath: '/api/uploads'
|
|
170
|
-
preLoaderRoute: typeof ApiUploadsRouteImport
|
|
171
|
-
parentRoute: typeof rootRouteImport
|
|
172
|
-
}
|
|
173
|
-
'/api/auth/$': {
|
|
174
|
-
id: '/api/auth/$'
|
|
175
|
-
path: '/api/auth/$'
|
|
176
|
-
fullPath: '/api/auth/$'
|
|
177
|
-
preLoaderRoute: typeof ApiAuthSplatRouteImport
|
|
178
|
-
parentRoute: typeof rootRouteImport
|
|
179
|
-
}
|
|
180
|
-
'/api/centrifugo/connect': {
|
|
181
|
-
id: '/api/centrifugo/connect'
|
|
182
|
-
path: '/api/centrifugo/connect'
|
|
183
|
-
fullPath: '/api/centrifugo/connect'
|
|
184
|
-
preLoaderRoute: typeof ApiCentrifugoConnectRouteImport
|
|
185
|
-
parentRoute: typeof rootRouteImport
|
|
186
|
-
}
|
|
187
|
-
'/api/uploads/$id': {
|
|
188
|
-
id: '/api/uploads/$id'
|
|
189
|
-
path: '/$id'
|
|
190
|
-
fullPath: '/api/uploads/$id'
|
|
191
|
-
preLoaderRoute: typeof ApiUploadsIdRouteImport
|
|
192
|
-
parentRoute: typeof ApiUploadsRoute
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
interface ApiUploadsRouteChildren {
|
|
198
|
-
ApiUploadsIdRoute: typeof ApiUploadsIdRoute
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const ApiUploadsRouteChildren: ApiUploadsRouteChildren = {
|
|
202
|
-
ApiUploadsIdRoute: ApiUploadsIdRoute,
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
const ApiUploadsRouteWithChildren = ApiUploadsRoute._addFileChildren(
|
|
206
|
-
ApiUploadsRouteChildren,
|
|
207
|
-
)
|
|
208
|
-
|
|
209
|
-
const rootRouteChildren: RootRouteChildren = {
|
|
210
|
-
IndexRoute: IndexRoute,
|
|
211
|
-
ApiHealthRoute: ApiHealthRoute,
|
|
212
|
-
ApiLiveRoute: ApiLiveRoute,
|
|
213
|
-
ApiReadyRoute: ApiReadyRoute,
|
|
214
|
-
ApiUploadsRoute: ApiUploadsRouteWithChildren,
|
|
215
|
-
ApiAuthSplatRoute: ApiAuthSplatRoute,
|
|
216
|
-
ApiCentrifugoConnectRoute: ApiCentrifugoConnectRoute,
|
|
217
|
-
}
|
|
218
|
-
export const routeTree = rootRouteImport
|
|
219
|
-
._addFileChildren(rootRouteChildren)
|
|
220
|
-
._addFileTypes<FileRouteTypes>()
|
|
221
|
-
|
|
222
|
-
import type { getRouter } from './router.tsx'
|
|
223
|
-
import type { startInstance } from './start.ts'
|
|
224
|
-
declare module '@tanstack/react-start' {
|
|
225
|
-
interface Register {
|
|
226
|
-
ssr: true
|
|
227
|
-
router: Awaited<ReturnType<typeof getRouter>>
|
|
228
|
-
config: Awaited<ReturnType<typeof startInstance.getOptions>>
|
|
229
|
-
}
|
|
230
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
import { createRouter } from '@tanstack/react-router'
|
|
2
|
-
import { setupRouterSsrQueryIntegration } from '@tanstack/react-router-ssr-query'
|
|
3
|
-
import { createQueryClient } from './client/queryClient'
|
|
4
|
-
import { routeTree } from './routeTree.gen'
|
|
5
|
-
|
|
6
|
-
export function getRouter() {
|
|
7
|
-
const queryClient = createQueryClient()
|
|
8
|
-
const router = createRouter({ routeTree, context: { queryClient }, defaultPreload: 'intent' })
|
|
9
|
-
setupRouterSsrQueryIntegration({ router, queryClient })
|
|
10
|
-
return router
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
declare module '@tanstack/react-router' {
|
|
14
|
-
interface Register {
|
|
15
|
-
router: ReturnType<typeof getRouter>
|
|
16
|
-
}
|
|
17
|
-
}
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
import type { QueryClient } from '@tanstack/react-query'
|
|
2
|
-
import { HeadContent, Outlet, Scripts, createRootRouteWithContext } from '@tanstack/react-router'
|
|
3
|
-
import { postHogEnvironment } from 'ras-stack/posthog'
|
|
4
|
-
import { PostHogIntegration } from 'ras-stack/posthog/react'
|
|
5
|
-
import styles from '../styles.css?url'
|
|
6
|
-
|
|
7
|
-
const posthog = postHogEnvironment({
|
|
8
|
-
projectToken: import.meta.env.VITE_POSTHOG_PROJECT_TOKEN,
|
|
9
|
-
host: import.meta.env.VITE_POSTHOG_HOST,
|
|
10
|
-
})
|
|
11
|
-
|
|
12
|
-
const posthogService = { name: 'ras-stack-example-web', environment: import.meta.env.MODE }
|
|
13
|
-
|
|
14
|
-
export const Route = createRootRouteWithContext<{ queryClient: QueryClient }>()({
|
|
15
|
-
head: () => ({
|
|
16
|
-
meta: [
|
|
17
|
-
{ charSet: 'utf-8' },
|
|
18
|
-
{ name: 'viewport', content: 'width=device-width, initial-scale=1' },
|
|
19
|
-
{ title: 'ras-stack full-stack example' },
|
|
20
|
-
],
|
|
21
|
-
links: [{ rel: 'stylesheet', href: styles }],
|
|
22
|
-
}),
|
|
23
|
-
component: Root,
|
|
24
|
-
})
|
|
25
|
-
|
|
26
|
-
function Root() {
|
|
27
|
-
return (
|
|
28
|
-
<html lang="en">
|
|
29
|
-
<head>
|
|
30
|
-
<HeadContent />
|
|
31
|
-
</head>
|
|
32
|
-
<body>
|
|
33
|
-
<PostHogIntegration environment={posthog} service={posthogService}>
|
|
34
|
-
<Outlet />
|
|
35
|
-
</PostHogIntegration>
|
|
36
|
-
<Scripts />
|
|
37
|
-
</body>
|
|
38
|
-
</html>
|
|
39
|
-
)
|
|
40
|
-
}
|
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
-
import { app } from '../../server/app'
|
|
3
|
-
|
|
4
|
-
const handler = ({ request }: { request: Request }) => app().auth.handler(request)
|
|
5
|
-
|
|
6
|
-
export const Route = createFileRoute('/api/auth/$')({
|
|
7
|
-
server: { handlers: { GET: handler, POST: handler } },
|
|
8
|
-
})
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
-
import crypto from 'node:crypto'
|
|
3
|
-
import { requireTanStackMutationOrigin } from 'ras-stack/tanstack/server'
|
|
4
|
-
import { currentUser } from '../../server/session'
|
|
5
|
-
|
|
6
|
-
export const Route = createFileRoute('/api/centrifugo/connect')({
|
|
7
|
-
server: {
|
|
8
|
-
handlers: {
|
|
9
|
-
POST: async ({ request }) => {
|
|
10
|
-
if (!proxySecret(request)) return Response.json({ disconnect: { code: 4501, reason: 'unauthorized' } })
|
|
11
|
-
try {
|
|
12
|
-
requireTanStackMutationOrigin({ configured: [process.env.APP_URL], trustForwardedHeaders: true }, request)
|
|
13
|
-
} catch {
|
|
14
|
-
return Response.json({ disconnect: { code: 4501, reason: 'unauthorized' } })
|
|
15
|
-
}
|
|
16
|
-
const user = await currentUser(request)
|
|
17
|
-
return user
|
|
18
|
-
? Response.json({ result: { user: user.id, info: { name: user.name } } })
|
|
19
|
-
: Response.json({ disconnect: { code: 4501, reason: 'unauthorized' } })
|
|
20
|
-
},
|
|
21
|
-
},
|
|
22
|
-
},
|
|
23
|
-
})
|
|
24
|
-
|
|
25
|
-
function proxySecret(request: Request) {
|
|
26
|
-
const expected = Buffer.from(process.env.CENTRIFUGO_PROXY_SECRET ?? '')
|
|
27
|
-
const received = Buffer.from(request.headers.get('x-proxy-secret') ?? '')
|
|
28
|
-
return expected.length > 0 && expected.length === received.length && crypto.timingSafeEqual(expected, received)
|
|
29
|
-
}
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import { createFileRoute } from '@tanstack/react-router'
|
|
2
|
-
import { sql } from 'drizzle-orm'
|
|
3
|
-
import { infrastructureFailure } from 'ras-stack/server'
|
|
4
|
-
import { tanStackHealthHandler } from 'ras-stack/tanstack/server'
|
|
5
|
-
import { app } from '../../server/app'
|
|
6
|
-
import { outboxStatus } from '../../server/outbox'
|
|
7
|
-
|
|
8
|
-
export const readinessHandler = tanStackHealthHandler(
|
|
9
|
-
async () => {
|
|
10
|
-
try {
|
|
11
|
-
app().database.get(sql`SELECT 1`)
|
|
12
|
-
const status = outboxStatus(app().database)
|
|
13
|
-
if (status.failed > 0) throw new Error(`Realtime outbox has ${status.failed} dead-lettered item(s)`)
|
|
14
|
-
await app().email?.verify()
|
|
15
|
-
} catch (error) {
|
|
16
|
-
console.error({ event: 'example_readiness_failed', error })
|
|
17
|
-
throw error
|
|
18
|
-
}
|
|
19
|
-
},
|
|
20
|
-
{
|
|
21
|
-
failure: (error) =>
|
|
22
|
-
infrastructureFailure(error, { code: 'dependency_unavailable', message: 'required dependency unavailable', retryable: true }),
|
|
23
|
-
},
|
|
24
|
-
)
|
|
25
|
-
|
|
26
|
-
export const Route = createFileRoute('/api/ready')({
|
|
27
|
-
server: {
|
|
28
|
-
handlers: { GET: readinessHandler },
|
|
29
|
-
},
|
|
30
|
-
})
|