ras-stack 0.8.3 → 0.10.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 +27 -4
- package/dist/database/index.d.ts +8 -0
- package/dist/database/index.js +11 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/sqlite.d.ts +25 -0
- package/dist/database/sqlite.js +49 -0
- package/dist/database/sqlite.js.map +1 -0
- package/dist/policy/cli.js +4 -4
- package/dist/policy/cli.js.map +1 -1
- package/dist/policy/index.d.ts +9 -0
- package/dist/policy/index.js +82 -1
- package/dist/policy/index.js.map +1 -1
- package/package.json +20 -1
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
|
|
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
|
|
@@ -88,6 +90,27 @@ Applications still own their auth clients, authorization, routes, router, loggin
|
|
|
88
90
|
|
|
89
91
|
Only enable `trustForwardedHeaders` behind a proxy that replaces incoming forwarded headers. Otherwise a client could choose the origin used by the check.
|
|
90
92
|
|
|
93
|
+
## Database lifecycle
|
|
94
|
+
|
|
95
|
+
The SQLite entrypoint owns the native client lifecycle, standard safety PRAGMAs, and optional Drizzle migrations while returning the upstream typed database:
|
|
96
|
+
|
|
97
|
+
```ts
|
|
98
|
+
import { bundledDirectory } from 'ras-stack/database'
|
|
99
|
+
import { openDrizzleSqlite } from 'ras-stack/database/sqlite'
|
|
100
|
+
|
|
101
|
+
const database = openDrizzleSqlite({
|
|
102
|
+
file,
|
|
103
|
+
schema,
|
|
104
|
+
migrationsFolder: bundledDirectory({
|
|
105
|
+
developmentUrl: new URL('../../drizzle', import.meta.url),
|
|
106
|
+
production: import.meta.env.PROD,
|
|
107
|
+
name: 'drizzle',
|
|
108
|
+
}),
|
|
109
|
+
})
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
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.
|
|
113
|
+
|
|
91
114
|
## Realtime updates
|
|
92
115
|
|
|
93
116
|
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 +259,14 @@ The JavaScript setup action reads the Node version from `engines.node` and the p
|
|
|
236
259
|
```yaml
|
|
237
260
|
steps:
|
|
238
261
|
- uses: actions/checkout@v7
|
|
239
|
-
- uses: richardsolomou/ras-stack/actions/setup-js@v0.
|
|
262
|
+
- uses: richardsolomou/ras-stack/actions/setup-js@v0.9.0
|
|
240
263
|
- run: pnpm check
|
|
241
264
|
```
|
|
242
265
|
|
|
243
266
|
Just is independent of the application language and is installed separately when a repository uses it:
|
|
244
267
|
|
|
245
268
|
```yaml
|
|
246
|
-
- uses: richardsolomou/ras-stack/actions/setup-just@v0.
|
|
269
|
+
- uses: richardsolomou/ras-stack/actions/setup-just@v0.9.0
|
|
247
270
|
with:
|
|
248
271
|
version: '1.58.0'
|
|
249
272
|
```
|
|
@@ -256,7 +279,7 @@ release:
|
|
|
256
279
|
needs: [check]
|
|
257
280
|
permissions:
|
|
258
281
|
contents: write
|
|
259
|
-
uses: richardsolomou/ras-stack/.github/workflows/release-changesets.yml@v0.
|
|
282
|
+
uses: richardsolomou/ras-stack/.github/workflows/release-changesets.yml@v0.9.0
|
|
260
283
|
secrets: inherit
|
|
261
284
|
```
|
|
262
285
|
|
|
@@ -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"]}
|
package/dist/policy/cli.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import { syncRepositoryPolicy } from './index.js';
|
|
2
|
+
import { checkRepositoryPolicy, syncRepositoryPolicy } from './index.js';
|
|
3
3
|
const command = process.argv[2];
|
|
4
4
|
if (command !== 'check' && command !== 'sync') {
|
|
5
5
|
console.error('usage: ras-stack-policy <check|sync>');
|
|
6
6
|
process.exitCode = 2;
|
|
7
7
|
}
|
|
8
8
|
else {
|
|
9
|
-
const changed =
|
|
9
|
+
const changed = command === 'check' ? await checkRepositoryPolicy(process.cwd()) : await syncRepositoryPolicy(process.cwd(), 'write');
|
|
10
10
|
if (command === 'check' && changed.length > 0) {
|
|
11
|
-
for (const
|
|
12
|
-
console.error(
|
|
11
|
+
for (const message of changed)
|
|
12
|
+
console.error(message);
|
|
13
13
|
console.error('run ras-stack-policy sync and commit the result');
|
|
14
14
|
process.exitCode = 1;
|
|
15
15
|
}
|
package/dist/policy/cli.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/policy/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/policy/cli.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAA;AAExE,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAA;AAC/B,IAAI,OAAO,KAAK,OAAO,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;IAC9C,OAAO,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAA;IACrD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAA;AACtB,CAAC;KAAM,CAAC;IACN,MAAM,OAAO,GAAG,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,MAAM,qBAAqB,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,oBAAoB,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,OAAO,CAAC,CAAA;IACrI,IAAI,OAAO,KAAK,OAAO,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC9C,KAAK,MAAM,OAAO,IAAI,OAAO;YAAE,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;QACrD,OAAO,CAAC,KAAK,CAAC,iDAAiD,CAAC,CAAA;QAChE,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAA;IACtB,CAAC;SAAM,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;QAC9B,KAAK,MAAM,IAAI,IAAI,OAAO;YAAE,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,EAAE,CAAC,CAAA;IAC5D,CAAC;AACH,CAAC","sourcesContent":["#!/usr/bin/env node\nimport { checkRepositoryPolicy, syncRepositoryPolicy } from './index.js'\n\nconst command = process.argv[2]\nif (command !== 'check' && command !== 'sync') {\n console.error('usage: ras-stack-policy <check|sync>')\n process.exitCode = 2\n} else {\n const changed = command === 'check' ? await checkRepositoryPolicy(process.cwd()) : await syncRepositoryPolicy(process.cwd(), 'write')\n if (command === 'check' && changed.length > 0) {\n for (const message of changed) console.error(message)\n console.error('run ras-stack-policy sync and commit the result')\n process.exitCode = 1\n } else if (command === 'sync') {\n for (const path of changed) console.log(`updated ${path}`)\n }\n}\n"]}
|
package/dist/policy/index.d.ts
CHANGED
|
@@ -7,7 +7,16 @@ export type RepositoryPolicy = {
|
|
|
7
7
|
pnpm?: false | {
|
|
8
8
|
minimumReleaseAge?: number;
|
|
9
9
|
};
|
|
10
|
+
adoption?: false | AdoptionPolicy;
|
|
11
|
+
};
|
|
12
|
+
export type AdoptionPolicy = {
|
|
13
|
+
minimumRasStackVersion?: string;
|
|
14
|
+
node?: string;
|
|
15
|
+
pnpm?: string;
|
|
16
|
+
just?: string;
|
|
10
17
|
};
|
|
11
18
|
export declare function syncRepositoryPolicy(root: string, mode: 'check' | 'write'): Promise<string[]>;
|
|
19
|
+
export declare function checkRepositoryPolicy(root: string): Promise<string[]>;
|
|
20
|
+
export declare function adoptionDrift(root: string, policy: AdoptionPolicy): Promise<string[]>;
|
|
12
21
|
export declare function renderedPolicyFiles(root: string, config: RepositoryPolicy): Promise<Map<string, string>>;
|
|
13
22
|
export {};
|
package/dist/policy/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
1
|
+
import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises';
|
|
2
2
|
import { dirname, join } from 'node:path';
|
|
3
3
|
import { parseDocument, stringify } from 'yaml';
|
|
4
4
|
const changesetsPolicy = {
|
|
@@ -46,6 +46,43 @@ export async function syncRepositoryPolicy(root, mode) {
|
|
|
46
46
|
}));
|
|
47
47
|
return compared.filter((path) => path !== undefined);
|
|
48
48
|
}
|
|
49
|
+
export async function checkRepositoryPolicy(root) {
|
|
50
|
+
const files = await syncRepositoryPolicy(root, 'check');
|
|
51
|
+
const config = repositoryPolicy(await readFile(join(root, 'ras-stack.policy.json'), 'utf8'));
|
|
52
|
+
return [...files.map((path) => `policy drift: ${path}`), ...(config.adoption ? await adoptionDrift(root, config.adoption) : [])];
|
|
53
|
+
}
|
|
54
|
+
export async function adoptionDrift(root, policy) {
|
|
55
|
+
const drift = [];
|
|
56
|
+
const manifest = JSON.parse(await readFile(join(root, 'package.json'), 'utf8'));
|
|
57
|
+
if (policy.node && manifest.engines && plainObject(manifest.engines) && manifest.engines.node !== policy.node) {
|
|
58
|
+
drift.push(`toolchain drift: package.json engines.node must be ${policy.node}`);
|
|
59
|
+
}
|
|
60
|
+
if (policy.pnpm && manifest.packageManager !== `pnpm@${policy.pnpm}`) {
|
|
61
|
+
drift.push(`toolchain drift: package.json packageManager must be pnpm@${policy.pnpm}`);
|
|
62
|
+
}
|
|
63
|
+
if (policy.minimumRasStackVersion) {
|
|
64
|
+
const dependency = dependencyVersion(manifest, 'ras-stack');
|
|
65
|
+
if (dependency && compareVersions(dependency, policy.minimumRasStackVersion) < 0) {
|
|
66
|
+
drift.push(`ras-stack drift: package.json uses ${dependency}, minimum is ${policy.minimumRasStackVersion}`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
const workflows = await textFiles(join(root, '.github'));
|
|
70
|
+
for (const [path, source] of workflows) {
|
|
71
|
+
if (policy.minimumRasStackVersion) {
|
|
72
|
+
for (const match of source.matchAll(/richardsolomou\/ras-stack\/[^\s'"}]+@v(\d+\.\d+\.\d+)/g)) {
|
|
73
|
+
const version = match[1];
|
|
74
|
+
if (version && compareVersions(version, policy.minimumRasStackVersion) < 0) {
|
|
75
|
+
drift.push(`ras-stack drift: ${path} uses v${version}, minimum is v${policy.minimumRasStackVersion}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
if (policy.just &&
|
|
81
|
+
![...workflows.values()].some((source) => source.includes(`just-version: '${policy.just}'`) || source.includes(`just-version: ${policy.just}`))) {
|
|
82
|
+
drift.push(`toolchain drift: no workflow declares just-version ${policy.just}`);
|
|
83
|
+
}
|
|
84
|
+
return drift;
|
|
85
|
+
}
|
|
49
86
|
export async function renderedPolicyFiles(root, config) {
|
|
50
87
|
const files = new Map();
|
|
51
88
|
if (config.changesets) {
|
|
@@ -77,8 +114,52 @@ function repositoryPolicy(source) {
|
|
|
77
114
|
throw new Error('pnpm.minimumReleaseAge must be a non-negative integer');
|
|
78
115
|
}
|
|
79
116
|
}
|
|
117
|
+
const adoption = value.adoption;
|
|
118
|
+
if (adoption !== undefined && adoption !== false) {
|
|
119
|
+
if (!plainObject(adoption))
|
|
120
|
+
throw new Error('adoption policy must be false or an object');
|
|
121
|
+
for (const key of ['minimumRasStackVersion', 'node', 'pnpm', 'just']) {
|
|
122
|
+
if (adoption[key] !== undefined && typeof adoption[key] !== 'string')
|
|
123
|
+
throw new Error(`adoption.${key} must be a string`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
80
126
|
return value;
|
|
81
127
|
}
|
|
128
|
+
function dependencyVersion(manifest, name) {
|
|
129
|
+
for (const field of ['dependencies', 'devDependencies']) {
|
|
130
|
+
const dependencies = manifest[field];
|
|
131
|
+
const value = plainObject(dependencies) ? dependencies[name] : undefined;
|
|
132
|
+
if (typeof value === 'string')
|
|
133
|
+
return /\d+\.\d+\.\d+/.exec(value)?.[0];
|
|
134
|
+
}
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
function compareVersions(left, right) {
|
|
138
|
+
const a = left.split('.').map(Number);
|
|
139
|
+
const b = right.split('.').map(Number);
|
|
140
|
+
for (let index = 0; index < 3; index += 1) {
|
|
141
|
+
if (a[index] !== b[index])
|
|
142
|
+
return (a[index] ?? 0) - (b[index] ?? 0);
|
|
143
|
+
}
|
|
144
|
+
return 0;
|
|
145
|
+
}
|
|
146
|
+
async function textFiles(directory, root = directory) {
|
|
147
|
+
const files = new Map();
|
|
148
|
+
const entries = await readdir(directory, { withFileTypes: true }).catch(() => []);
|
|
149
|
+
const results = await Promise.all(entries.map(async (entry) => {
|
|
150
|
+
const path = join(directory, entry.name);
|
|
151
|
+
if (entry.isDirectory())
|
|
152
|
+
return textFiles(path, root);
|
|
153
|
+
if (/\.ya?ml$/.test(entry.name))
|
|
154
|
+
return new Map([[path.slice(root.length + 1), await readFile(path, 'utf8')]]);
|
|
155
|
+
return new Map();
|
|
156
|
+
}));
|
|
157
|
+
for (const result of results) {
|
|
158
|
+
for (const [path, source] of result)
|
|
159
|
+
files.set(path, source);
|
|
160
|
+
}
|
|
161
|
+
return files;
|
|
162
|
+
}
|
|
82
163
|
function validateSelection(name, value) {
|
|
83
164
|
if (value === undefined || typeof value === 'boolean')
|
|
84
165
|
return;
|
package/dist/policy/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/policy/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdir, readFile, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport { parseDocument, stringify } from 'yaml'\n\nconst changesetsPolicy = {\n $schema: 'https://unpkg.com/@changesets/config@3.1.2/schema.json',\n changelog: '@changesets/cli/changelog',\n commit: false,\n fixed: [],\n linked: [],\n access: 'public',\n baseBranch: 'main',\n updateInternalDependencies: 'patch',\n ignore: [],\n}\n\nconst dependabotPolicy = {\n version: 2,\n updates: [\n {\n 'package-ecosystem': 'npm',\n directory: '/',\n schedule: { interval: 'weekly' },\n cooldown: { 'default-days': 7 },\n groups: { 'javascript-dependencies': { patterns: ['*'] } },\n },\n {\n 'package-ecosystem': 'github-actions',\n directory: '/',\n schedule: { interval: 'weekly' },\n cooldown: { 'default-days': 7 },\n },\n ],\n}\n\ntype PolicySelection = boolean | { overrides?: Record<string, unknown> }\n\nexport type RepositoryPolicy = {\n changesets?: PolicySelection\n dependabot?: PolicySelection\n pnpm?: false | { minimumReleaseAge?: number }\n}\n\nexport async function syncRepositoryPolicy(root: string, mode: 'check' | 'write') {\n const config = repositoryPolicy(await readFile(join(root, 'ras-stack.policy.json'), 'utf8'))\n const files = await renderedPolicyFiles(root, config)\n const compared = await Promise.all(\n [...files].map(async ([path, expected]) => {\n const absolute = join(root, path)\n const actual = await readFile(absolute, 'utf8').catch(() => undefined)\n if (actual === expected) return undefined\n if (mode === 'write') {\n await mkdir(dirname(absolute), { recursive: true })\n await writeFile(absolute, expected)\n }\n return path\n }),\n )\n return compared.filter((path) => path !== undefined)\n}\n\nexport async function renderedPolicyFiles(root: string, config: RepositoryPolicy) {\n const files = new Map<string, string>()\n if (config.changesets) {\n files.set('.changeset/config.json', `${JSON.stringify(selectedPolicy(changesetsPolicy, config.changesets), null, 2)}\\n`)\n }\n if (config.dependabot) {\n files.set('.github/dependabot.yml', stringify(selectedPolicy(dependabotPolicy, config.dependabot), { lineWidth: 0, singleQuote: true }))\n }\n if (config.pnpm) {\n const path = join(root, 'pnpm-workspace.yaml')\n const document = parseDocument(await readFile(path, 'utf8').catch(() => ''))\n document.set('minimumReleaseAge', config.pnpm.minimumReleaseAge ?? 10_080)\n files.set('pnpm-workspace.yaml', document.toString({ lineWidth: 0 }))\n }\n return files\n}\n\nfunction repositoryPolicy(source: string): RepositoryPolicy {\n const value = JSON.parse(source) as unknown\n if (!plainObject(value)) throw new Error('ras-stack.policy.json must contain an object')\n for (const name of ['changesets', 'dependabot'] as const) validateSelection(name, value[name])\n const pnpm = value.pnpm\n if (pnpm !== undefined && pnpm !== false) {\n if (!plainObject(pnpm)) throw new Error('pnpm policy must be false or an object')\n const age = pnpm.minimumReleaseAge\n if (age !== undefined && (typeof age !== 'number' || !Number.isInteger(age) || age < 0)) {\n throw new Error('pnpm.minimumReleaseAge must be a non-negative integer')\n }\n }\n return value\n}\n\nfunction validateSelection(name: string, value: unknown) {\n if (value === undefined || typeof value === 'boolean') return\n if (!plainObject(value) || (value.overrides !== undefined && !plainObject(value.overrides))) {\n throw new Error(`${name} policy must be a boolean or an object with overrides`)\n }\n}\n\nfunction selectedPolicy(base: Record<string, unknown>, selection: Exclude<PolicySelection, false>) {\n return selection === true ? base : deepMerge(base, selection.overrides ?? {})\n}\n\nfunction deepMerge(base: Record<string, unknown>, overrides: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n [...new Set([...Object.keys(base), ...Object.keys(overrides)])].map((key) => {\n const left = base[key]\n const right = overrides[key]\n return [key, plainObject(left) && plainObject(right) ? deepMerge(left, right) : right === undefined ? left : right]\n }),\n )\n}\n\nfunction plainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n"]}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/policy/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { mkdir, readFile, readdir, writeFile } from 'node:fs/promises'\nimport { dirname, join } from 'node:path'\nimport { parseDocument, stringify } from 'yaml'\n\nconst changesetsPolicy = {\n $schema: 'https://unpkg.com/@changesets/config@3.1.2/schema.json',\n changelog: '@changesets/cli/changelog',\n commit: false,\n fixed: [],\n linked: [],\n access: 'public',\n baseBranch: 'main',\n updateInternalDependencies: 'patch',\n ignore: [],\n}\n\nconst dependabotPolicy = {\n version: 2,\n updates: [\n {\n 'package-ecosystem': 'npm',\n directory: '/',\n schedule: { interval: 'weekly' },\n cooldown: { 'default-days': 7 },\n groups: { 'javascript-dependencies': { patterns: ['*'] } },\n },\n {\n 'package-ecosystem': 'github-actions',\n directory: '/',\n schedule: { interval: 'weekly' },\n cooldown: { 'default-days': 7 },\n },\n ],\n}\n\ntype PolicySelection = boolean | { overrides?: Record<string, unknown> }\n\nexport type RepositoryPolicy = {\n changesets?: PolicySelection\n dependabot?: PolicySelection\n pnpm?: false | { minimumReleaseAge?: number }\n adoption?: false | AdoptionPolicy\n}\n\nexport type AdoptionPolicy = {\n minimumRasStackVersion?: string\n node?: string\n pnpm?: string\n just?: string\n}\n\nexport async function syncRepositoryPolicy(root: string, mode: 'check' | 'write') {\n const config = repositoryPolicy(await readFile(join(root, 'ras-stack.policy.json'), 'utf8'))\n const files = await renderedPolicyFiles(root, config)\n const compared = await Promise.all(\n [...files].map(async ([path, expected]) => {\n const absolute = join(root, path)\n const actual = await readFile(absolute, 'utf8').catch(() => undefined)\n if (actual === expected) return undefined\n if (mode === 'write') {\n await mkdir(dirname(absolute), { recursive: true })\n await writeFile(absolute, expected)\n }\n return path\n }),\n )\n return compared.filter((path) => path !== undefined)\n}\n\nexport async function checkRepositoryPolicy(root: string) {\n const files = await syncRepositoryPolicy(root, 'check')\n const config = repositoryPolicy(await readFile(join(root, 'ras-stack.policy.json'), 'utf8'))\n return [...files.map((path) => `policy drift: ${path}`), ...(config.adoption ? await adoptionDrift(root, config.adoption) : [])]\n}\n\nexport async function adoptionDrift(root: string, policy: AdoptionPolicy) {\n const drift: string[] = []\n const manifest = JSON.parse(await readFile(join(root, 'package.json'), 'utf8')) as Record<string, unknown>\n if (policy.node && manifest.engines && plainObject(manifest.engines) && manifest.engines.node !== policy.node) {\n drift.push(`toolchain drift: package.json engines.node must be ${policy.node}`)\n }\n if (policy.pnpm && manifest.packageManager !== `pnpm@${policy.pnpm}`) {\n drift.push(`toolchain drift: package.json packageManager must be pnpm@${policy.pnpm}`)\n }\n if (policy.minimumRasStackVersion) {\n const dependency = dependencyVersion(manifest, 'ras-stack')\n if (dependency && compareVersions(dependency, policy.minimumRasStackVersion) < 0) {\n drift.push(`ras-stack drift: package.json uses ${dependency}, minimum is ${policy.minimumRasStackVersion}`)\n }\n }\n\n const workflows = await textFiles(join(root, '.github'))\n for (const [path, source] of workflows) {\n if (policy.minimumRasStackVersion) {\n for (const match of source.matchAll(/richardsolomou\\/ras-stack\\/[^\\s'\"}]+@v(\\d+\\.\\d+\\.\\d+)/g)) {\n const version = match[1]\n if (version && compareVersions(version, policy.minimumRasStackVersion) < 0) {\n drift.push(`ras-stack drift: ${path} uses v${version}, minimum is v${policy.minimumRasStackVersion}`)\n }\n }\n }\n }\n if (\n policy.just &&\n ![...workflows.values()].some(\n (source) => source.includes(`just-version: '${policy.just}'`) || source.includes(`just-version: ${policy.just}`),\n )\n ) {\n drift.push(`toolchain drift: no workflow declares just-version ${policy.just}`)\n }\n return drift\n}\n\nexport async function renderedPolicyFiles(root: string, config: RepositoryPolicy) {\n const files = new Map<string, string>()\n if (config.changesets) {\n files.set('.changeset/config.json', `${JSON.stringify(selectedPolicy(changesetsPolicy, config.changesets), null, 2)}\\n`)\n }\n if (config.dependabot) {\n files.set('.github/dependabot.yml', stringify(selectedPolicy(dependabotPolicy, config.dependabot), { lineWidth: 0, singleQuote: true }))\n }\n if (config.pnpm) {\n const path = join(root, 'pnpm-workspace.yaml')\n const document = parseDocument(await readFile(path, 'utf8').catch(() => ''))\n document.set('minimumReleaseAge', config.pnpm.minimumReleaseAge ?? 10_080)\n files.set('pnpm-workspace.yaml', document.toString({ lineWidth: 0 }))\n }\n return files\n}\n\nfunction repositoryPolicy(source: string): RepositoryPolicy {\n const value = JSON.parse(source) as unknown\n if (!plainObject(value)) throw new Error('ras-stack.policy.json must contain an object')\n for (const name of ['changesets', 'dependabot'] as const) validateSelection(name, value[name])\n const pnpm = value.pnpm\n if (pnpm !== undefined && pnpm !== false) {\n if (!plainObject(pnpm)) throw new Error('pnpm policy must be false or an object')\n const age = pnpm.minimumReleaseAge\n if (age !== undefined && (typeof age !== 'number' || !Number.isInteger(age) || age < 0)) {\n throw new Error('pnpm.minimumReleaseAge must be a non-negative integer')\n }\n }\n const adoption = value.adoption\n if (adoption !== undefined && adoption !== false) {\n if (!plainObject(adoption)) throw new Error('adoption policy must be false or an object')\n for (const key of ['minimumRasStackVersion', 'node', 'pnpm', 'just']) {\n if (adoption[key] !== undefined && typeof adoption[key] !== 'string') throw new Error(`adoption.${key} must be a string`)\n }\n }\n return value\n}\n\nfunction dependencyVersion(manifest: Record<string, unknown>, name: string) {\n for (const field of ['dependencies', 'devDependencies']) {\n const dependencies = manifest[field]\n const value = plainObject(dependencies) ? dependencies[name] : undefined\n if (typeof value === 'string') return /\\d+\\.\\d+\\.\\d+/.exec(value)?.[0]\n }\n return undefined\n}\n\nfunction compareVersions(left: string, right: string) {\n const a = left.split('.').map(Number)\n const b = right.split('.').map(Number)\n for (let index = 0; index < 3; index += 1) {\n if (a[index] !== b[index]) return (a[index] ?? 0) - (b[index] ?? 0)\n }\n return 0\n}\n\nasync function textFiles(directory: string, root = directory): Promise<Map<string, string>> {\n const files = new Map<string, string>()\n const entries = await readdir(directory, { withFileTypes: true }).catch(() => [])\n const results = await Promise.all(\n entries.map(async (entry) => {\n const path = join(directory, entry.name)\n if (entry.isDirectory()) return textFiles(path, root)\n if (/\\.ya?ml$/.test(entry.name)) return new Map([[path.slice(root.length + 1), await readFile(path, 'utf8')]])\n return new Map<string, string>()\n }),\n )\n for (const result of results) {\n for (const [path, source] of result) files.set(path, source)\n }\n return files\n}\n\nfunction validateSelection(name: string, value: unknown) {\n if (value === undefined || typeof value === 'boolean') return\n if (!plainObject(value) || (value.overrides !== undefined && !plainObject(value.overrides))) {\n throw new Error(`${name} policy must be a boolean or an object with overrides`)\n }\n}\n\nfunction selectedPolicy(base: Record<string, unknown>, selection: Exclude<PolicySelection, false>) {\n return selection === true ? base : deepMerge(base, selection.overrides ?? {})\n}\n\nfunction deepMerge(base: Record<string, unknown>, overrides: Record<string, unknown>): Record<string, unknown> {\n return Object.fromEntries(\n [...new Set([...Object.keys(base), ...Object.keys(overrides)])].map((key) => {\n const left = base[key]\n const right = overrides[key]\n return [key, plainObject(left) && plainObject(right) ? deepMerge(left, right) : right === undefined ? left : right]\n }),\n )\n}\n\nfunction plainObject(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "ras-stack",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.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
|
},
|