ras-stack 0.9.0 → 0.11.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 CHANGED
@@ -16,6 +16,7 @@ The package contains independent helpers for common application infrastructure:
16
16
  - **Server requests:** same-origin mutation guards, error-normalizing RPC wrappers, canonical-host redirects, and health responses.
17
17
  - **Realtime:** Centrifugo client lifecycle, token signing, presence synchronization, and a bounded publisher with retries and graceful shutdown.
18
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.
19
20
  - **Uploads:** a promise-based wrapper around resumable `tus-js-client` uploads.
20
21
  - **Project configuration:** shared TypeScript and Oxlint bases.
21
22
 
@@ -35,12 +36,13 @@ The libraries underneath remain available normally. Applications still configure
35
36
  pnpm add ras-stack
36
37
  ```
37
38
 
38
- Nodemailer, Centrifuge, and `tus-js-client` are optional peer dependencies. Install one only when using its integration:
39
+ Nodemailer, Centrifuge, Better SQLite/Drizzle, and `tus-js-client` are optional peer dependencies. Install them only when using their integrations:
39
40
 
40
41
  ```sh
41
42
  pnpm add nodemailer
42
43
  pnpm add centrifuge
43
44
  pnpm add tus-js-client
45
+ pnpm add better-sqlite3 drizzle-orm
44
46
  ```
45
47
 
46
48
  ## Authentication and request security
@@ -68,7 +70,13 @@ The optional TanStack entrypoints bind the shared primitives to TanStack Start's
68
70
 
69
71
  ```ts
70
72
  import { createStackQueryClient } from 'ras-stack/tanstack/query'
71
- import { createTanStackRpc, requireTanStackMutationOrigin } from 'ras-stack/tanstack/server'
73
+ import {
74
+ betterAuthHandlers,
75
+ canonicalHostMiddleware,
76
+ createTanStackRpc,
77
+ requireTanStackMutationOrigin,
78
+ tanStackHealthHandler,
79
+ } from 'ras-stack/tanstack/server'
72
80
 
73
81
  export const { rpc, mutationRpc } = createTanStackRpc({
74
82
  requireMutation: (request) =>
@@ -82,12 +90,37 @@ export const { rpc, mutationRpc } = createTanStackRpc({
82
90
  })
83
91
 
84
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 }))
85
97
  ```
86
98
 
87
- Applications still own their auth clients, authorization, routes, router, logging, health checks, and Query configuration. Both integrations remain optional, and their upstream libraries remain directly accessible.
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.
88
100
 
89
101
  Only enable `trustForwardedHeaders` behind a proxy that replaces incoming forwarded headers. Otherwise a client could choose the origin used by the check.
90
102
 
103
+ ## Database lifecycle
104
+
105
+ The SQLite entrypoint owns the native client lifecycle, standard safety PRAGMAs, and optional Drizzle migrations while returning the upstream typed database:
106
+
107
+ ```ts
108
+ import { bundledDirectory } from 'ras-stack/database'
109
+ import { openDrizzleSqlite } from 'ras-stack/database/sqlite'
110
+
111
+ const database = openDrizzleSqlite({
112
+ file,
113
+ schema,
114
+ migrationsFolder: bundledDirectory({
115
+ developmentUrl: new URL('../../drizzle', import.meta.url),
116
+ production: import.meta.env.PROD,
117
+ name: 'drizzle',
118
+ }),
119
+ })
120
+ ```
121
+
122
+ 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.
123
+
91
124
  ## Realtime updates
92
125
 
93
126
  Applications choose their channel names, authorize subscriptions, and define payloads. `ras-stack` handles Centrifugo's HTTP publication, signed tokens, and repeated browser lifecycle mechanics:
@@ -236,14 +269,14 @@ The JavaScript setup action reads the Node version from `engines.node` and the p
236
269
  ```yaml
237
270
  steps:
238
271
  - uses: actions/checkout@v7
239
- - uses: richardsolomou/ras-stack/actions/setup-js@v0.3.0
272
+ - uses: richardsolomou/ras-stack/actions/setup-js@v0.9.0
240
273
  - run: pnpm check
241
274
  ```
242
275
 
243
276
  Just is independent of the application language and is installed separately when a repository uses it:
244
277
 
245
278
  ```yaml
246
- - uses: richardsolomou/ras-stack/actions/setup-just@v0.2.0
279
+ - uses: richardsolomou/ras-stack/actions/setup-just@v0.9.0
247
280
  with:
248
281
  version: '1.58.0'
249
282
  ```
@@ -256,7 +289,7 @@ release:
256
289
  needs: [check]
257
290
  permissions:
258
291
  contents: write
259
- uses: richardsolomou/ras-stack/.github/workflows/release-changesets.yml@v0.3.0
292
+ uses: richardsolomou/ras-stack/.github/workflows/release-changesets.yml@v0.9.0
260
293
  secrets: inherit
261
294
  ```
262
295
 
@@ -0,0 +1,8 @@
1
+ import { type URL } from 'node:url';
2
+ export type BundledDirectoryOptions = {
3
+ developmentUrl: URL;
4
+ production: boolean;
5
+ productionEntry?: string;
6
+ name: string;
7
+ };
8
+ export declare function bundledDirectory(options: BundledDirectoryOptions): string;
@@ -0,0 +1,11 @@
1
+ import path from 'node:path';
2
+ import { fileURLToPath } from 'node:url';
3
+ export function bundledDirectory(options) {
4
+ if (!options.production)
5
+ return fileURLToPath(options.developmentUrl);
6
+ const entry = options.productionEntry ?? process.argv[1];
7
+ if (!entry)
8
+ throw new Error('production entrypoint is required to resolve a bundled directory');
9
+ return path.join(path.dirname(entry), options.name);
10
+ }
11
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/database/index.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAA;AAC5B,OAAO,EAAE,aAAa,EAAY,MAAM,UAAU,CAAA;AASlD,MAAM,UAAU,gBAAgB,CAAC,OAAgC;IAC/D,IAAI,CAAC,OAAO,CAAC,UAAU;QAAE,OAAO,aAAa,CAAC,OAAO,CAAC,cAAc,CAAC,CAAA;IACrE,MAAM,KAAK,GAAG,OAAO,CAAC,eAAe,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;IACxD,IAAI,CAAC,KAAK;QAAE,MAAM,IAAI,KAAK,CAAC,kEAAkE,CAAC,CAAA;IAC/F,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC,IAAI,CAAC,CAAA;AACrD,CAAC","sourcesContent":["import path from 'node:path'\nimport { fileURLToPath, type URL } from 'node:url'\n\nexport type BundledDirectoryOptions = {\n developmentUrl: URL\n production: boolean\n productionEntry?: string\n name: string\n}\n\nexport function bundledDirectory(options: BundledDirectoryOptions) {\n if (!options.production) return fileURLToPath(options.developmentUrl)\n const entry = options.productionEntry ?? process.argv[1]\n if (!entry) throw new Error('production entrypoint is required to resolve a bundled directory')\n return path.join(path.dirname(entry), options.name)\n}\n"]}
@@ -0,0 +1,25 @@
1
+ import Database from 'better-sqlite3';
2
+ import { type BetterSQLite3Database } from 'drizzle-orm/better-sqlite3';
3
+ export type SqlitePragmas = {
4
+ journalMode?: 'DELETE' | 'TRUNCATE' | 'PERSIST' | 'MEMORY' | 'WAL' | 'OFF';
5
+ synchronous?: 'OFF' | 'NORMAL' | 'FULL' | 'EXTRA';
6
+ busyTimeout?: number;
7
+ foreignKeys?: boolean;
8
+ };
9
+ export type OpenSqliteOptions = {
10
+ database?: Database.Options;
11
+ pragmas?: SqlitePragmas | false;
12
+ };
13
+ export declare function openSqliteClient(file: string, options?: OpenSqliteOptions): Database.Database;
14
+ export declare function configureSqlite(client: Database.Database, pragmas?: SqlitePragmas): Database.Database;
15
+ export type OpenDrizzleSqliteOptions<TSchema extends Record<string, unknown>> = OpenSqliteOptions & {
16
+ file: string;
17
+ schema: TSchema;
18
+ migrationsFolder?: string;
19
+ };
20
+ export declare function openDrizzleSqlite<TSchema extends Record<string, unknown>>(options: OpenDrizzleSqliteOptions<TSchema>): BetterSQLite3Database<TSchema> & {
21
+ $client: Database.Database;
22
+ };
23
+ export declare function closeDrizzleSqlite(database: {
24
+ $client: Database.Database;
25
+ }): void;
@@ -0,0 +1,49 @@
1
+ import Database from 'better-sqlite3';
2
+ import { drizzle } from 'drizzle-orm/better-sqlite3';
3
+ import { migrate } from 'drizzle-orm/better-sqlite3/migrator';
4
+ import { mkdirSync } from 'node:fs';
5
+ import path from 'node:path';
6
+ export function openSqliteClient(file, options = {}) {
7
+ if (file !== ':memory:')
8
+ mkdirSync(path.dirname(path.resolve(file)), { recursive: true });
9
+ const client = new Database(file, options.database);
10
+ try {
11
+ if (options.pragmas !== false)
12
+ configureSqlite(client, options.pragmas);
13
+ return client;
14
+ }
15
+ catch (error) {
16
+ client.close();
17
+ throw error;
18
+ }
19
+ }
20
+ export function configureSqlite(client, pragmas = {}) {
21
+ const journalMode = pragmas.journalMode ?? 'WAL';
22
+ const synchronous = pragmas.synchronous ?? 'FULL';
23
+ const busyTimeout = pragmas.busyTimeout ?? 5000;
24
+ const foreignKeys = pragmas.foreignKeys ?? true;
25
+ if (!Number.isInteger(busyTimeout) || busyTimeout < 0)
26
+ throw new Error('busyTimeout must be a non-negative integer');
27
+ client.pragma(`journal_mode = ${journalMode}`);
28
+ client.pragma(`synchronous = ${synchronous}`);
29
+ client.pragma(`busy_timeout = ${busyTimeout}`);
30
+ client.pragma(`foreign_keys = ${foreignKeys ? 'ON' : 'OFF'}`);
31
+ return client;
32
+ }
33
+ export function openDrizzleSqlite(options) {
34
+ const client = openSqliteClient(options.file, options);
35
+ try {
36
+ const database = drizzle({ client, schema: options.schema });
37
+ if (options.migrationsFolder)
38
+ migrate(database, { migrationsFolder: options.migrationsFolder });
39
+ return database;
40
+ }
41
+ catch (error) {
42
+ client.close();
43
+ throw error;
44
+ }
45
+ }
46
+ export function closeDrizzleSqlite(database) {
47
+ database.$client.close();
48
+ }
49
+ //# sourceMappingURL=sqlite.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sqlite.js","sourceRoot":"","sources":["../../src/database/sqlite.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,gBAAgB,CAAA;AACrC,OAAO,EAAE,OAAO,EAA8B,MAAM,4BAA4B,CAAA;AAChF,OAAO,EAAE,OAAO,EAAE,MAAM,qCAAqC,CAAA;AAC7D,OAAO,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AACnC,OAAO,IAAI,MAAM,WAAW,CAAA;AAc5B,MAAM,UAAU,gBAAgB,CAAC,IAAY,EAAE,OAAO,GAAsB,EAAE;IAC5E,IAAI,IAAI,KAAK,UAAU;QAAE,SAAS,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAA;IACzF,MAAM,MAAM,GAAG,IAAI,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;IACnD,IAAI,CAAC;QACH,IAAI,OAAO,CAAC,OAAO,KAAK,KAAK;YAAE,eAAe,CAAC,MAAM,EAAE,OAAO,CAAC,OAAO,CAAC,CAAA;QACvE,OAAO,MAAM,CAAA;IACf,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,EAAE,CAAA;QACd,MAAM,KAAK,CAAA;IACb,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,MAAyB,EAAE,OAAO,GAAkB,EAAE;IACpF,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,KAAK,CAAA;IAChD,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,MAAM,CAAA;IACjD,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,IAAI,CAAA;IAC/C,MAAM,WAAW,GAAG,OAAO,CAAC,WAAW,IAAI,IAAI,CAAA;IAC/C,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,WAAW,CAAC,IAAI,WAAW,GAAG,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,4CAA4C,CAAC,CAAA;IACpH,MAAM,CAAC,MAAM,CAAC,kBAAkB,WAAW,EAAE,CAAC,CAAA;IAC9C,MAAM,CAAC,MAAM,CAAC,iBAAiB,WAAW,EAAE,CAAC,CAAA;IAC7C,MAAM,CAAC,MAAM,CAAC,kBAAkB,WAAW,EAAE,CAAC,CAAA;IAC9C,MAAM,CAAC,MAAM,CAAC,kBAAkB,WAAW,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAA;IAC7D,OAAO,MAAM,CAAA;AACf,CAAC;AAQD,MAAM,UAAU,iBAAiB,CAC/B,OAA0C;IAE1C,MAAM,MAAM,GAAG,gBAAgB,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IACtD,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAA;QAC5D,IAAI,OAAO,CAAC,gBAAgB;YAAE,OAAO,CAAC,QAAQ,EAAE,EAAE,gBAAgB,EAAE,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAA;QAC/F,OAAO,QAAQ,CAAA;IACjB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,KAAK,EAAE,CAAA;QACd,MAAM,KAAK,CAAA;IACb,CAAC;AACH,CAAC;AAED,MAAM,UAAU,kBAAkB,CAAC,QAAwC;IACzE,QAAQ,CAAC,OAAO,CAAC,KAAK,EAAE,CAAA;AAC1B,CAAC","sourcesContent":["import Database from 'better-sqlite3'\nimport { drizzle, type BetterSQLite3Database } from 'drizzle-orm/better-sqlite3'\nimport { migrate } from 'drizzle-orm/better-sqlite3/migrator'\nimport { mkdirSync } from 'node:fs'\nimport path from 'node:path'\n\nexport type SqlitePragmas = {\n journalMode?: 'DELETE' | 'TRUNCATE' | 'PERSIST' | 'MEMORY' | 'WAL' | 'OFF'\n synchronous?: 'OFF' | 'NORMAL' | 'FULL' | 'EXTRA'\n busyTimeout?: number\n foreignKeys?: boolean\n}\n\nexport type OpenSqliteOptions = {\n database?: Database.Options\n pragmas?: SqlitePragmas | false\n}\n\nexport function openSqliteClient(file: string, options: OpenSqliteOptions = {}): Database.Database {\n if (file !== ':memory:') mkdirSync(path.dirname(path.resolve(file)), { recursive: true })\n const client = new Database(file, options.database)\n try {\n if (options.pragmas !== false) configureSqlite(client, options.pragmas)\n return client\n } catch (error) {\n client.close()\n throw error\n }\n}\n\nexport function configureSqlite(client: Database.Database, pragmas: SqlitePragmas = {}): Database.Database {\n const journalMode = pragmas.journalMode ?? 'WAL'\n const synchronous = pragmas.synchronous ?? 'FULL'\n const busyTimeout = pragmas.busyTimeout ?? 5000\n const foreignKeys = pragmas.foreignKeys ?? true\n if (!Number.isInteger(busyTimeout) || busyTimeout < 0) throw new Error('busyTimeout must be a non-negative integer')\n client.pragma(`journal_mode = ${journalMode}`)\n client.pragma(`synchronous = ${synchronous}`)\n client.pragma(`busy_timeout = ${busyTimeout}`)\n client.pragma(`foreign_keys = ${foreignKeys ? 'ON' : 'OFF'}`)\n return client\n}\n\nexport type OpenDrizzleSqliteOptions<TSchema extends Record<string, unknown>> = OpenSqliteOptions & {\n file: string\n schema: TSchema\n migrationsFolder?: string\n}\n\nexport function openDrizzleSqlite<TSchema extends Record<string, unknown>>(\n options: OpenDrizzleSqliteOptions<TSchema>,\n): BetterSQLite3Database<TSchema> & { $client: Database.Database } {\n const client = openSqliteClient(options.file, options)\n try {\n const database = drizzle({ client, schema: options.schema })\n if (options.migrationsFolder) migrate(database, { migrationsFolder: options.migrationsFolder })\n return database\n } catch (error) {\n client.close()\n throw error\n }\n}\n\nexport function closeDrizzleSqlite(database: { $client: Database.Database }) {\n database.$client.close()\n}\n"]}
@@ -1,8 +1,21 @@
1
1
  import { type OriginOptions } from '../auth/index.js';
2
- import { type RpcOptions } from '../server/index.js';
2
+ import { type CanonicalRedirectOptions, type HealthResponseOptions, type RpcOptions } from '../server/index.js';
3
3
  export type TanStackRpcOptions = Omit<RpcOptions, 'getRequest'> & Pick<RpcOptions, 'getRequest'>;
4
4
  export declare function createTanStackRpc(options?: TanStackRpcOptions): {
5
5
  rpc: <T>(work: () => Promise<T> | T) => Promise<T>;
6
6
  mutationRpc: <T>(work: () => Promise<T> | T, request?: Request | undefined) => Promise<T>;
7
7
  };
8
8
  export declare function requireTanStackMutationOrigin(options?: OriginOptions, request?: Request): void;
9
+ type RequestContext = {
10
+ request: Request;
11
+ };
12
+ export declare function betterAuthHandlers(resolveAuth: () => {
13
+ handler(request: Request): Response | Promise<Response>;
14
+ }): {
15
+ GET: ({ request }: RequestContext) => Promise<Response> | Response;
16
+ POST: ({ request }: RequestContext) => Promise<Response> | Response;
17
+ };
18
+ export declare function tanStackHealthHandler(check: () => void | Promise<void>, options?: HealthResponseOptions): () => Promise<Response>;
19
+ export declare function canonicalHostRequest<T>(request: Request, next: () => T, options: CanonicalRedirectOptions): Response | T;
20
+ export declare function canonicalHostMiddleware(resolveOptions: () => CanonicalRedirectOptions): import("@tanstack/react-start").RequestMiddlewareAfterServer<{}, undefined, undefined>;
21
+ export {};
@@ -1,10 +1,27 @@
1
1
  import { getRequest } from '@tanstack/react-start/server';
2
+ import { createMiddleware } from '@tanstack/react-start';
2
3
  import { requireSameOrigin } from '../auth/index.js';
3
- import { createRpc } from '../server/index.js';
4
+ import { canonicalRedirect, createRpc, healthResponse, } from '../server/index.js';
4
5
  export function createTanStackRpc(options = {}) {
5
6
  return createRpc({ ...options, getRequest: options.getRequest ?? getRequest });
6
7
  }
7
8
  export function requireTanStackMutationOrigin(options = {}, request = getRequest()) {
8
9
  return requireSameOrigin(request, options);
9
10
  }
11
+ export function betterAuthHandlers(resolveAuth) {
12
+ const handle = ({ request }) => resolveAuth().handler(request);
13
+ return { GET: handle, POST: handle };
14
+ }
15
+ export function tanStackHealthHandler(check, options = {}) {
16
+ return () => healthResponse(check, options);
17
+ }
18
+ export function canonicalHostRequest(request, next, options) {
19
+ const redirect = canonicalRedirect(request.url, options);
20
+ return redirect ? Response.redirect(redirect, 301) : next();
21
+ }
22
+ export function canonicalHostMiddleware(resolveOptions) {
23
+ return createMiddleware({ type: 'request' }).server(({ request, next }) => {
24
+ return canonicalHostRequest(request, next, resolveOptions());
25
+ });
26
+ }
10
27
  //# sourceMappingURL=server.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/tanstack/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAA;AACzD,OAAO,EAAE,iBAAiB,EAAsB,MAAM,kBAAkB,CAAA;AACxE,OAAO,EAAE,SAAS,EAAmB,MAAM,oBAAoB,CAAA;AAI/D,MAAM,UAAU,iBAAiB,CAAC,OAAO,GAAuB,EAAE;IAChE,OAAO,SAAS,CAAC,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,UAAU,EAAE,CAAC,CAAA;AAChF,CAAC;AAED,MAAM,UAAU,6BAA6B,CAAC,OAAO,GAAkB,EAAE,EAAE,OAAO,GAAG,UAAU,EAAE;IAC/F,OAAO,iBAAiB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;AAC5C,CAAC","sourcesContent":["import { getRequest } from '@tanstack/react-start/server'\nimport { requireSameOrigin, type OriginOptions } from '../auth/index.js'\nimport { createRpc, type RpcOptions } from '../server/index.js'\n\nexport type TanStackRpcOptions = Omit<RpcOptions, 'getRequest'> & Pick<RpcOptions, 'getRequest'>\n\nexport function createTanStackRpc(options: TanStackRpcOptions = {}) {\n return createRpc({ ...options, getRequest: options.getRequest ?? getRequest })\n}\n\nexport function requireTanStackMutationOrigin(options: OriginOptions = {}, request = getRequest()) {\n return requireSameOrigin(request, options)\n}\n"]}
1
+ {"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/tanstack/server.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,8BAA8B,CAAA;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AACxD,OAAO,EAAE,iBAAiB,EAAsB,MAAM,kBAAkB,CAAA;AACxE,OAAO,EACL,iBAAiB,EACjB,SAAS,EACT,cAAc,GAIf,MAAM,oBAAoB,CAAA;AAI3B,MAAM,UAAU,iBAAiB,CAAC,OAAO,GAAuB,EAAE;IAChE,OAAO,SAAS,CAAC,EAAE,GAAG,OAAO,EAAE,UAAU,EAAE,OAAO,CAAC,UAAU,IAAI,UAAU,EAAE,CAAC,CAAA;AAChF,CAAC;AAED,MAAM,UAAU,6BAA6B,CAAC,OAAO,GAAkB,EAAE,EAAE,OAAO,GAAG,UAAU,EAAE;IAC/F,OAAO,iBAAiB,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;AAC5C,CAAC;AAID,MAAM,UAAU,kBAAkB,CAAC,WAA8E;IAC/G,MAAM,MAAM,GAAG,CAAC,EAAE,OAAO,EAAkB,EAAE,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,CAAA;IAC9E,OAAO,EAAE,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,CAAA;AACtC,CAAC;AAED,MAAM,UAAU,qBAAqB,CAAC,KAAiC,EAAE,OAAO,GAA0B,EAAE;IAC1G,OAAO,GAAG,EAAE,CAAC,cAAc,CAAC,KAAK,EAAE,OAAO,CAAC,CAAA;AAC7C,CAAC;AAED,MAAM,UAAU,oBAAoB,CAAI,OAAgB,EAAE,IAAa,EAAE,OAAiC;IACxG,MAAM,QAAQ,GAAG,iBAAiB,CAAC,OAAO,CAAC,GAAG,EAAE,OAAO,CAAC,CAAA;IACxD,OAAO,QAAQ,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAA;AAC7D,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,cAA8C;IACpF,OAAO,gBAAgB,CAAC,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,EAAE;QACxE,OAAO,oBAAoB,CAAC,OAAO,EAAE,IAAI,EAAE,cAAc,EAAE,CAAC,CAAA;IAC9D,CAAC,CAAC,CAAA;AACJ,CAAC","sourcesContent":["import { getRequest } from '@tanstack/react-start/server'\nimport { createMiddleware } from '@tanstack/react-start'\nimport { requireSameOrigin, type OriginOptions } from '../auth/index.js'\nimport {\n canonicalRedirect,\n createRpc,\n healthResponse,\n type CanonicalRedirectOptions,\n type HealthResponseOptions,\n type RpcOptions,\n} from '../server/index.js'\n\nexport type TanStackRpcOptions = Omit<RpcOptions, 'getRequest'> & Pick<RpcOptions, 'getRequest'>\n\nexport function createTanStackRpc(options: TanStackRpcOptions = {}) {\n return createRpc({ ...options, getRequest: options.getRequest ?? getRequest })\n}\n\nexport function requireTanStackMutationOrigin(options: OriginOptions = {}, request = getRequest()) {\n return requireSameOrigin(request, options)\n}\n\ntype RequestContext = { request: Request }\n\nexport function betterAuthHandlers(resolveAuth: () => { handler(request: Request): Response | Promise<Response> }) {\n const handle = ({ request }: RequestContext) => resolveAuth().handler(request)\n return { GET: handle, POST: handle }\n}\n\nexport function tanStackHealthHandler(check: () => void | Promise<void>, options: HealthResponseOptions = {}) {\n return () => healthResponse(check, options)\n}\n\nexport function canonicalHostRequest<T>(request: Request, next: () => T, options: CanonicalRedirectOptions): Response | T {\n const redirect = canonicalRedirect(request.url, options)\n return redirect ? Response.redirect(redirect, 301) : next()\n}\n\nexport function canonicalHostMiddleware(resolveOptions: () => CanonicalRedirectOptions) {\n return createMiddleware({ type: 'request' }).server(({ request, next }) => {\n return canonicalHostRequest(request, next, resolveOptions())\n })\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ras-stack",
3
- "version": "0.9.0",
3
+ "version": "0.11.0",
4
4
  "description": "Composable full-stack primitives shared across Richard Solomou's applications.",
5
5
  "keywords": [
6
6
  "authentication",
@@ -34,6 +34,14 @@
34
34
  "types": "./dist/email/index.d.ts",
35
35
  "default": "./dist/email/index.js"
36
36
  },
37
+ "./database": {
38
+ "types": "./dist/database/index.d.ts",
39
+ "default": "./dist/database/index.js"
40
+ },
41
+ "./database/sqlite": {
42
+ "types": "./dist/database/sqlite.d.ts",
43
+ "default": "./dist/database/sqlite.js"
44
+ },
37
45
  "./policy": {
38
46
  "types": "./dist/policy/index.d.ts",
39
47
  "default": "./dist/policy/index.js"
@@ -94,9 +102,12 @@
94
102
  "@changesets/cli": "^2.31.1",
95
103
  "@tanstack/react-query": "^5.101.4",
96
104
  "@tanstack/react-start": "^1.168.32",
105
+ "@types/better-sqlite3": "9.6.0",
97
106
  "@types/node": "^26.0.0",
98
107
  "@types/nodemailer": "^8.0.1",
108
+ "better-sqlite3": "12.11.1",
99
109
  "centrifuge": "5.7.0",
110
+ "drizzle-orm": "0.45.2",
100
111
  "nodemailer": "^9.0.3",
101
112
  "oxfmt": "^0.61.0",
102
113
  "oxlint": "^1.74.0",
@@ -109,7 +120,9 @@
109
120
  "peerDependencies": {
110
121
  "@tanstack/react-query": ">=5 <6",
111
122
  "@tanstack/react-start": ">=1 <2",
123
+ "better-sqlite3": ">=12 <13",
112
124
  "centrifuge": ">=5 <6",
125
+ "drizzle-orm": ">=0.45 <1",
113
126
  "nodemailer": ">=9 <10",
114
127
  "tus-js-client": ">=4 <5"
115
128
  },
@@ -120,9 +133,15 @@
120
133
  "@tanstack/react-start": {
121
134
  "optional": true
122
135
  },
136
+ "better-sqlite3": {
137
+ "optional": true
138
+ },
123
139
  "centrifuge": {
124
140
  "optional": true
125
141
  },
142
+ "drizzle-orm": {
143
+ "optional": true
144
+ },
126
145
  "nodemailer": {
127
146
  "optional": true
128
147
  },