@getstrata/core 1.0.0 → 1.0.2

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.
Files changed (35) hide show
  1. package/CHANGELOG.md +52 -33
  2. package/README.md +36 -43
  3. package/dist/core/database/baseRepository.d.ts +2 -1
  4. package/dist/core/database/dialect.d.ts +5 -1
  5. package/dist/core/database/errors.d.ts +4 -0
  6. package/dist/core/database/mysqlConnection.d.ts +3 -1
  7. package/dist/core/errors/http.d.ts +4 -1
  8. package/dist/core/http/clientIp.d.ts +6 -3
  9. package/dist/core/http/webErrorResponse.d.ts +3 -1
  10. package/dist/core/runtime/appEnv.d.ts +4 -0
  11. package/dist/core/tenant/tenancyConfig.d.ts +1 -0
  12. package/dist/entries/audit/exportAuditLogs.js +7 -5
  13. package/dist/entries/auth/scimAuthMiddleware.js +7 -5
  14. package/dist/entries/contracts/authUserDirectory.js +1 -0
  15. package/dist/entries/database/errors.js +45 -6
  16. package/dist/entries/database/mysqlConnection.js +18 -2
  17. package/dist/entries/database/sqliteConnection.js +5 -0
  18. package/dist/entries/http/clientIp.js +24 -6
  19. package/dist/entries/http/corsMiddleware.js +14 -3
  20. package/dist/entries/http/loginThrottleMiddleware.js +25 -8
  21. package/dist/entries/http/memoryThrottleMiddleware.js +25 -8
  22. package/dist/entries/http/response.js +66 -7
  23. package/dist/entries/http/scimThrottleMiddleware.js +23 -6
  24. package/dist/entries/http/throttleMiddleware.js +25 -8
  25. package/dist/entries/http/webErrorResponse.js +66 -7
  26. package/dist/entries/jobs/exportAuditLogsJob.js +7 -5
  27. package/dist/entries/logging/requestLoggingMiddleware.js +29 -10
  28. package/dist/entries/openapi/generator.js +1 -1
  29. package/dist/entries/runtime/appEnv.js +8 -0
  30. package/dist/entries/tenant/databaseTenantContext.js +7 -5
  31. package/dist/entries/tenant/tenancyConfig.js +7 -5
  32. package/dist/entries/tenant/tenantDatabaseScope.js +7 -5
  33. package/dist/framework/public-api.d.ts +3 -3
  34. package/dist/index.js +161 -39
  35. package/package.json +16 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,24 @@
1
1
  # @getstrata/core changelog
2
2
 
3
+ ## 1.0.2
4
+
5
+ - Lockstep with `create-strata` 1.0.2. No runtime changes.
6
+
7
+ ## 1.0.1
8
+
9
+ - Publish the `contracts/authUserDirectory` subpath. Generated cookie and token apps import `AuthUserDirectory` from it, and without the export their `tsc --noEmit` failed with TS2307.
10
+ - `SqlDialect` gains `timestampValue()`, plus a `sqlTimestamp()` helper. MySQL `DATETIME` rejects the ISO-8601 `T` separator and trailing `Z`, which broke cookie session inserts on MySQL.
11
+ - `DatabaseConnection.close?()` now returns `void | Promise<void>`, matching the synchronous `close()` on the SQLite connection it is supposed to describe.
12
+ - `engines.bun` is declared.
13
+ - MySQL pools are UTC end to end: `createMysqlConnection()` passes `timezone: "Z"` to mysql2 and runs `SET time_zone = '+00:00'` on every new connection. Before, `DATETIME` values written as UTC by `timestampValue()` were read back shifted by the host offset, and `expires_at > NOW()` depended on the server time zone. `createMysqlPool(url)` exposes the configured raw pool.
14
+ - SQLite `nowExpression()` is `strftime('%Y-%m-%dT%H:%M:%fZ', 'now')` instead of `CURRENT_TIMESTAMP`, matching the ISO-8601 text that `timestampValue()` writes. With `CURRENT_TIMESTAMP` the `T` separator sorted after the space, so an expired session compared as valid until the next UTC day.
15
+ - Unknown exceptions map to `InternalServerError` (500, generic message) instead of a 400 that echoed the raw error text. SQLite (`SQLITE_CONSTRAINT_*`) and MySQL (`ER_DUP_ENTRY`, `ER_NO_REFERENCED_ROW_2`, `ER_ROW_IS_REFERENCED_2`, `ER_BAD_NULL_ERROR`, `ER_CHECK_CONSTRAINT_VIOLATED`) constraint errors map to 409/422/400 like Postgres SQLSTATEs. 5xx responses are logged with the original message and stack.
16
+ - Client IP: `readClientIp()` falls back to the socket address the web server records in the request context, and with `TRUST_FORWARDED_FOR=true` takes the rightmost public `X-Forwarded-For` hop instead of the first (client-controlled) one. Throttles no longer collapse every client into an `unknown` bucket.
17
+ - CORS: with `CORS_ALLOWED_ORIGINS` unset, production sends no `Access-Control-Allow-Origin` (same-origin only) instead of reflecting any origin; outside production the default stays `*`. Listed origins are reflected; unlisted ones get no header.
18
+ - `TENANCY_DRIVER` must be `none`, `column`, or `rls`. Unknown values throw instead of enabling rls.
19
+ - SQLite connections enable WAL, `busy_timeout = 5000`, and `synchronous = NORMAL` for file databases.
20
+ - New subpath `runtime/appEnv` with `isProductionEnv()` (`APP_ENV` or `NODE_ENV`), used for the cookie `Secure` flag and for hiding 500 messages in HTML.
21
+
3
22
  ## 1.0.0
4
23
 
5
24
  - First stable release of the public `@getstrata/core` API.
@@ -19,7 +38,7 @@
19
38
 
20
39
  ## 0.7.2
21
40
 
22
- - `withJsonErrorHandling` always maps thrown errors to JSON. `requestPrefersJson` treats `/api/` as JSON even when `Accept` is HTML, so hybrid staff HTML cannot remap JSON API errors.
41
+ - `withJsonErrorHandling` always maps thrown errors to JSON. `requestPrefersJson` treats `/api/` as JSON even when `Accept` is HTML, so hybrid HTML cannot remap JSON API errors.
23
42
 
24
43
  ## 0.7.1
25
44
 
@@ -28,20 +47,20 @@
28
47
 
29
48
  ## 0.7.0
30
49
 
31
- - `FRONTEND_MODE=hybrid` turns on staff HTML and the candidate SPA together. Views are on for `server-htmx` and `hybrid`. The SPA is on for `spa-react` and `hybrid`.
50
+ - `FRONTEND_MODE=hybrid` turns on HTML at `/` and a SPA under `SPA_PREFIX` together. Views are on for `server-htmx` and `hybrid`. The SPA is on for `spa-react` and `hybrid`.
32
51
  - `parseFrontendMode`, `FRONTEND_MODES`, and `FRONTEND_MODE_PATTERN` are the allowed-value list. Apps should reuse that pattern in env schemas.
33
52
  - Named database connections: `registerNamedConnection`, `runOnNamedConnection`, SQLite (`bun:sqlite`), and MySQL (`mysql2`). `runWithSqlDialect` keeps the dialect across `await` via AsyncLocalStorage.
34
53
  - New subpaths: `database/namedConnections`, `database/sqliteConnection`, `database/mysqlConnection`, `database/connectionContext`.
35
- - OpenAPI treats `POST /api/apply/login` as unauthenticated.
54
+ - OpenAPI treats unauthenticated login routes as public.
36
55
 
37
56
  ## 0.6.0
38
57
 
39
58
  - **Breaking:** cookie session signatures use HMAC-SHA256. Existing HMAC cookies signed with the previous digest will not verify. Rotate `SESSION_SECRET` or sign users in again.
40
- - **Breaking:** default `MEMBER_ABILITIES` are profile and token scopes (`profile:read`, `auth:tokens:*`), not org/project/task. Apps replace the catalog with `configureAbilityCatalog`. HiroApp maps admin / recruiter / candidate.
59
+ - **Breaking:** default `MEMBER_ABILITIES` are profile and token scopes (`profile:read`, `auth:tokens:*`), not org/project/task. Apps replace the catalog with `configureAbilityCatalog`. Generated HiroApp maps admin / member.
41
60
  - Named auth guards: opaque Bearer tokens, JWT HS256, HTTP Basic, and cookie sessions. `AuthManager` picks a guard from the `Authorization` scheme. CSRF is skipped for Bearer and Basic.
42
- - OpenAPI treats HiroApp login, JWT mint (`POST /api/auth/token`), and public careers as unauthenticated. Partner ping and audit export require credentials.
61
+ - OpenAPI treats HiroApp login and JWT mint (`POST /api/auth/token`) as unauthenticated. Partner ping and audit export require credentials.
43
62
  - SQL dialect helpers (`pgsql`, `mysql`, `sqlite`) for placeholders, quoting, `ILIKE`/`LIKE`, `RETURNING`, and `NULLS LAST`. Full-text `tsMatch` stays Postgres-only and throws on other engines.
44
- - HiroApp is the only in-repo example product. The leftover `src/db` schema is a test fixture, not a second app.
63
+ - HiroApp is the in-repo generated example. The leftover `src/db` schema is a test fixture, not a second app.
45
64
  - New subpaths: `auth/jwt`, `auth/jwtGuard`, `auth/basicAuthGuard`, `auth/tokenAbilityChecker`, `database/dialect`, `http/statelessAuth`.
46
65
 
47
66
  ## 0.5.101
@@ -60,12 +79,12 @@ Still non-conforming (honest): Bun cannot infer `morphTo()` method names. `hashe
60
79
 
61
80
  ## 0.5.99
62
81
 
63
- HiroApp dogfood of 0.5.98 found lookalike APIs. This release matches the previous PHP framework call shape and SQL, not just export names.
82
+ HiroApp dogfood of 0.5.98 found lookalike APIs. This release matches the existing call shape and SQL, not just export names.
64
83
 
65
84
  - Relation queries are thenable: `await user.applications()` delegates to `get()`.
66
85
  - `Model.with()` / `where()` / `whereHas()` return a `ModelQuery` that hydrates models and chains into `where` / `find` / `findOrFail` / `first` / `get`. `first()` / `find()` use `LIMIT` / PK lookup.
67
86
  - `belongsTo.where()` threads constraints into `whereHas` EXISTS (HiroApp application search).
68
- - `{ ilike }` uses the value as-is (the previous PHP framework). Pass `%term%` yourself; the operator no longer wraps extra `%`.
87
+ - `{ ilike }` uses the value as-is. Pass `%term%` yourself; the operator no longer wraps extra `%`.
69
88
  - Morph type defaults to `$morphClass` / class name, not `table.name`. Override with `$morphClass = "App\\Models\\User"` or the last `morphMany` argument. `morphTo()` no longer silently defaults to `imageable_*`.
70
89
  - `primaryKey()` defaults to the table PK (`id`).
71
90
  - Nested `load("a.b")` / `with("a.b")` skip already-loaded heads and batch the next level.
@@ -80,13 +99,13 @@ Still non-conforming (honest): Bun cannot infer `morphTo()` method names (`debug
80
99
 
81
100
  ## 0.5.98
82
101
 
83
- - Eloquent-shaped relations: `this.hasMany(Related)` returns a relation query (`get`/`where`/`create`/`attach`). `load()` / `loaded()` and `Model.with()` replace PHP `__get`. `whereHas`/`has`/`doesntHave`, morph* methods, and nested `with("a.b")` are supported.
102
+ - Model relations: `this.hasMany(Related)` returns a relation query (`get`/`where`/`create`/`attach`). `load()` / `loaded()` and `Model.with()` load related rows. `whereHas`/`has`/`doesntHave`, morph* methods, and nested `with("a.b")` are supported.
84
103
  - Model `$hidden`/`$visible`/`$appends`, `toArray()`/`toJSON()`, `makeHidden`/`makeVisible`/`append`, and `observe()`.
85
104
  - `Model.where()`, `firstOrNew()`, `firstOrCreate()`, and `updateOrCreate()`.
86
105
  - Factory `count`/`state`/`sequence`/`for`/`has`/`recycle` plus `afterMaking`/`afterCreating`.
87
106
  - `JsonResource` (`wrap`, `whenLoaded`, `additional`, `collection`).
88
- - the previous PHP framework aliases: container `make`/`instance`, EventBus `on`/`emit`, query `whereNull`/`whereIn`/`whereExists`.
89
- - Parity audit reports design score separately. Horizon/Nova/CLI stay Bun-native stand-ins.
107
+ - Convenience aliases: container `make`/`instance`, EventBus `on`/`emit`, query `whereNull`/`whereIn`/`whereExists`.
108
+ - Parity audit reports design score separately. Queue dashboards, admin UIs, and CLI stay Bun-native.
90
109
 
91
110
  ## 0.5.97
92
111
 
@@ -103,7 +122,7 @@ Still non-conforming (honest): Bun cannot infer `morphTo()` method names (`debug
103
122
 
104
123
  ## 0.5.90
105
124
 
106
- - **Breaking identity defaults:** `appKeyPrefix()` is `strata` and `appDisplayName()` is `Strata` when `APP_KEY_PREFIX` / `APP_NAME` are unset (were `strata` / `the previous in-repo app`). the previous in-repo app pins those env vars.
125
+ - **Breaking identity defaults:** `appKeyPrefix()` is `strata` and `appDisplayName()` is `Strata` when `APP_KEY_PREFIX` / `APP_NAME` are unset (were `strata` / `HiroApp`). HiroApp pins those env vars.
107
126
  - `Schedule.command()` rejects cron strings other than `* * * * *` and `*/N * * * *` instead of silently never running them.
108
127
  - OpenAPI marks `GET /users/me/*` as bearer-authenticated.
109
128
  - OpenAPI `/users/me/current-organization` summaries no longer say team invitations.
@@ -115,11 +134,11 @@ Still non-conforming (honest): Bun cannot infer `morphTo()` method names (`debug
115
134
  ## 0.5.88
116
135
 
117
136
  - Exported cookie name constants (`SESSION_COOKIE`, `CSRF_COOKIE`, `FLASH_COOKIE`, `INTENDED_URL_COOKIE`, `PASSWORD_CONFIRM_COOKIE`) are `appCookieName(...)` so they match the default prefix instead of a hardcoded `strata_` string.
118
- - `@getstrata/core/jobs/dispatchWebhookJob` is a deprecated compatibility re-export of the the previous in-repo app webhook job.
137
+ - `@getstrata/core/jobs/dispatchWebhookJob` is a deprecated compatibility re-export of the HiroApp webhook job.
119
138
 
120
139
  ## 0.5.87
121
140
 
122
- - `AuthUserDirectory.hasActiveBrowserSession?(userId, issuedAt)` is optional. `SessionGuard` calls it after `session_valid_after` and rejects the HMAC cookie when it returns false so the previous in-repo app can revoke a single browser session by deleting the `sessions` row.
141
+ - `AuthUserDirectory.hasActiveBrowserSession?(userId, issuedAt)` is optional. `SessionGuard` calls it after `session_valid_after` and rejects the HMAC cookie when it returns false so HiroApp can revoke a single browser session by deleting the `sessions` row.
123
142
 
124
143
  ## 0.5.86
125
144
 
@@ -144,11 +163,11 @@ Still non-conforming (honest): Bun cannot infer `morphTo()` method names (`debug
144
163
 
145
164
  ## 0.5.81
146
165
 
147
- - `@getstrata/core/auth/intendedUrlCookie` (`createIntendedUrlCookie`, `readIntendedUrl`, `clearIntendedUrlCookie`, `createIntendedUrlCookieFromRequest`). HTML auth intended URL after HTML email verification: register with `redirect=` and the previous PHP framework `verified` HTML redirects stash `${APP_KEY_PREFIX}_intended` (`INTENDED_URL_COOKIE_NAME`, default `strata_intended`; TTL `INTENDED_URL_TTL_SECONDS`, default 86400s). `GET /verify-email` honors and clears it.
166
+ - `@getstrata/core/auth/intendedUrlCookie` (`createIntendedUrlCookie`, `readIntendedUrl`, `clearIntendedUrlCookie`, `createIntendedUrlCookieFromRequest`). HTML auth intended URL after HTML email verification: register with `redirect=` and verified HTML redirects stash `${APP_KEY_PREFIX}_intended` (`INTENDED_URL_COOKIE_NAME`, default `strata_intended`; TTL `INTENDED_URL_TTL_SECONDS`, default 86400s). `GET /verify-email` honors and clears it.
148
167
 
149
168
  ## 0.5.80
150
169
 
151
- - `appCookieName()` / `appDevSecret()` on `@getstrata/core/runtime/appKeyPrefix`. HMAC session, CSRF, flash, password-confirm, signed-URL, OAuth-state, and token-pepper fallbacks follow `APP_KEY_PREFIX` (the previous in-repo app defaults unchanged).
170
+ - `appCookieName()` / `appDevSecret()` on `@getstrata/core/runtime/appKeyPrefix`. HMAC session, CSRF, flash, password-confirm, signed-URL, OAuth-state, and token-pepper fallbacks follow `APP_KEY_PREFIX` (HiroApp defaults unchanged).
152
171
 
153
172
  ## 0.5.79
154
173
 
@@ -168,12 +187,12 @@ Still non-conforming (honest): Bun cannot infer `morphTo()` method names (`debug
168
187
 
169
188
  ## 0.5.75
170
189
 
171
- - the previous PHP framework `password.confirm`: `@getstrata/core/auth/passwordConfirmCookie` (`createPasswordConfirmCookie`, `hasFreshPasswordConfirmation`, `clearPasswordConfirmCookie`) and `createRequirePasswordConfirmMiddleware()` (HTML 302 `/confirm-password`, JSON 423). Cookie name `PASSWORD_CONFIRM_COOKIE_NAME` (default `strata_password_confirmed`), TTL `PASSWORD_CONFIRM_TIMEOUT` (default 10800s).
190
+ - Password confirm: `@getstrata/core/auth/passwordConfirmCookie` (`createPasswordConfirmCookie`, `hasFreshPasswordConfirmation`, `clearPasswordConfirmCookie`) and `createRequirePasswordConfirmMiddleware()` (HTML 302 `/confirm-password`, JSON 423). Cookie name `PASSWORD_CONFIRM_COOKIE_NAME` (default `strata_password_confirmed`), TTL `PASSWORD_CONFIRM_TIMEOUT` (default 10800s).
172
191
 
173
192
  ## 0.5.74
174
193
 
175
194
  - `AuthUser.emailVerifiedAt` and `@getstrata/core/auth/emailVerification` (`isEmailVerificationRequired`, `hasVerifiedEmail`). `null` means unverified; missing is treated as verified (GuestGuard / legacy).
176
- - `createRequireVerifiedMiddleware()` is the previous PHP framework `verified` (HTML 302 `/email/verify`, JSON 403).
195
+ - `createRequireVerifiedMiddleware()` requires a verified email (HTML 302 `/email/verify`, JSON 403).
177
196
 
178
197
  ## 0.5.73
179
198
 
@@ -198,40 +217,40 @@ Still non-conforming (honest): Bun cannot infer `morphTo()` method names (`debug
198
217
 
199
218
  ## 0.5.68
200
219
 
201
- - Published core no longer imports the previous in-repo app `src/config`. Frontend mode, queue retries, CORS, and upload limits read env (`FRONTEND_MODE`, `QUEUE_*`, `CORS_ALLOWED_ORIGINS`, `MAX_UPLOAD_BYTES`).
220
+ - Published core no longer imports HiroApp `src/config`. Frontend mode, queue retries, CORS, and upload limits read env (`FRONTEND_MODE`, `QUEUE_*`, `CORS_ALLOWED_ORIGINS`, `MAX_UPLOAD_BYTES`).
202
221
  - `@getstrata/core/runtime/frontendMode` exports `readFrontendMode` / `isViewsEnabled` / `isSpaEnabled`.
203
222
 
204
223
  ## 0.5.67
205
224
 
206
- - OpenAPI title, server URLs, and generated SDK class name come from `APP_NAME` / `APP_URL` / `API_PREFIX` / `APP_SDK_CLASS` instead of importing the previous in-repo app `src/config/app`.
207
- - `appEnv()`, `appUrl()`, `apiPrefix()`, and `sdkClientClassName()` live on `@getstrata/core/runtime/appKeyPrefix`. SIEM export, HSTS, and `safeFetch` DNS resolve use `appEnv()` instead of the previous in-repo app `appConfig`.
225
+ - OpenAPI title, server URLs, and generated SDK class name come from `APP_NAME` / `APP_URL` / `API_PREFIX` / `APP_SDK_CLASS` instead of importing HiroApp `src/config/app`.
226
+ - `appEnv()`, `appUrl()`, `apiPrefix()`, and `sdkClientClassName()` live on `@getstrata/core/runtime/appKeyPrefix`. SIEM export, HSTS, and `safeFetch` DNS resolve use `appEnv()` instead of HiroApp `appConfig`.
208
227
 
209
228
  ## 0.5.66
210
229
 
211
- - `createValidateSignatureMiddleware()` on `@getstrata/core/http/signedUrl` is the previous PHP framework's `signed` / `ValidateSignature` middleware. Invalid or expired links throw `ForbiddenError`.
230
+ - `createValidateSignatureMiddleware()` on `@getstrata/core/http/signedUrl` rejects invalid or expired signed links with `ForbiddenError`.
212
231
 
213
232
  ## 0.5.65
214
233
 
215
- - Identity helpers on `@getstrata/core/runtime/appKeyPrefix` (`smtpEhloHost`, `siemEventType`, `appUserAgent`, `otelServiceName`, `appDisplayName`, `webhookSignatureHeader`) so sibling apps are not stuck with the previous in-repo app SMTP/SIEM/OTEL/OAuth names.
216
- - SIEM `event_type` and CEF vendor follow `SIEM_EVENT_TYPE` / `APP_NAME` (defaults stay `strata.audit` / `the previous in-repo app`).
234
+ - Identity helpers on `@getstrata/core/runtime/appKeyPrefix` (`smtpEhloHost`, `siemEventType`, `appUserAgent`, `otelServiceName`, `appDisplayName`, `webhookSignatureHeader`) so sibling apps are not stuck with HiroApp SMTP/SIEM/OTEL/OAuth names.
235
+ - SIEM `event_type` and CEF vendor follow `SIEM_EVENT_TYPE` / `APP_NAME` (defaults stay `strata.audit` / `HiroApp`).
217
236
 
218
237
  ## 0.5.64
219
238
 
220
- - `APP_KEY_PREFIX` (default `strata`) namespaces Redis cache, queue, and throttle keys so sibling apps do not share the previous in-repo app's keyspace.
221
- - `DispatchWebhookJob` implementation lives in the the previous in-repo app webhook module. `@getstrata/core/jobs/dispatchWebhookJob` remains a compatibility re-export.
239
+ - `APP_KEY_PREFIX` (default `strata`) namespaces Redis cache, queue, and throttle keys so sibling apps do not share HiroApp's keyspace.
240
+ - `DispatchWebhookJob` implementation lives in the HiroApp webhook module. `@getstrata/core/jobs/dispatchWebhookJob` remains a compatibility re-export.
222
241
 
223
242
  ## 0.5.63
224
243
 
225
- - Flash cookies honor `FLASH_COOKIE_NAME` (default `strata_flash`) so sibling apps do not inherit the previous in-repo app's cookie name.
244
+ - Flash cookies honor `FLASH_COOKIE_NAME` (default `strata_flash`) so sibling apps do not inherit HiroApp's cookie name.
226
245
 
227
246
  ## 0.5.62
228
247
 
229
- the previous PHP framework URL signing, Bun-native markdown mail, and session-auth cleanup.
248
+ URL signing, Bun-native markdown mail, and session-auth cleanup.
230
249
 
231
250
  - `temporarySignedUrl` / `signedUrl` / `hasValidSignature` / `assertValidSignature` on `@getstrata/core/http/signedUrl`. HMAC uses `SIGNED_URL_SECRET` or `SESSION_SECRET`. Paths must be same-origin (`/` only; reject `//` and `://`).
232
251
  - `markdownToHtml()` uses `Bun.markdown.html()` plus `sanitizeMailHtml` (allowlist). Scripts, `javascript:` links, and unknown tags are stripped.
233
252
  - `ExportAuditLogsJob` (`@getstrata/core/jobs/exportAuditLogsJob`) wraps SIEM export so the scheduler can dispatch a real job.
234
- - `DispatchWebhookJob` reads `APP_ENV` and `WEBHOOK_SIGNATURE_HEADER` instead of the previous in-repo app `appConfig`.
253
+ - `DispatchWebhookJob` reads `APP_ENV` and `WEBHOOK_SIGNATURE_HEADER` instead of HiroApp `appConfig`.
235
254
  - `createRequireWebAuthMiddleware` runs the handler inside `runWithAuthUser` so `currentAuthUser()` works on HTMX session routes.
236
255
  - `generateTotpSecret()` / `buildOtpauthUrl()` on `@getstrata/core/security/totp`.
237
256
  - `createRequireAbilityMiddleware` rethrows `ForbiddenError` for HTML views so HTMX routes render a styled 403 instead of JSON.
@@ -255,13 +274,13 @@ HTMX HTML kernel gaps that sibling apps could not work around without weakening
255
274
 
256
275
  ## 0.5.59
257
276
 
258
- Sibling HTMX apps can consume published packages without the previous in-repo app-only glue.
277
+ Sibling HTMX apps can consume published packages without HiroApp-only glue.
259
278
 
260
279
  - Emit `@getstrata/core/facades` types at `dist/core/facades/index.d.ts` (CI checks every `exports.*.types` path after build).
261
- - Session and CSRF cookie names are configurable (`SESSION_COOKIE_NAME`, `CSRF_COOKIE_NAME`). Defaults remain `strata_session` and `strata_csrf` for the previous in-repo app.
280
+ - Session and CSRF cookie names are configurable (`SESSION_COOKIE_NAME`, `CSRF_COOKIE_NAME`). Defaults remain `strata_session` and `strata_csrf` for HiroApp.
262
281
  - Split `CORE_ABILITY_CHECKER_TOKEN` and `CORE_AUTH_USER_DIRECTORY_TOKEN` from `CORE_TOKEN_SERVICE_TOKEN`. HttpKernel prefers the ability-checker token; HMAC `SessionGuard` / layout data prefer the user directory. A compatibility shim uses `CORE_TOKEN_SERVICE_TOKEN` only when the bound value matches the requested type.
263
- - `configureWebLayoutData` lets apps choose `currentUser` vs `authUser`, a custom user loader, and extra template fields. the previous in-repo app still gets `{ authUser, csrfToken, flash }`.
282
+ - `configureWebLayoutData` lets apps choose `currentUser` vs `authUser`, a custom user loader, and extra template fields. HiroApp still gets `{ authUser, csrfToken, flash }`.
264
283
  - `redirectResponse`, `notFoundHtmlResponse`, `textResponse`, and `xmlResponse` join `htmlResponse` / `isHtmxRequest` on `@getstrata/core/view`.
265
284
  - `LOGIN_RATE_LIMIT_WINDOW_MS` is a deprecated alias for `LOGIN_RATE_LIMIT_WINDOW_SECONDS`.
266
285
 
267
- HMAC `SessionGuard` is unchanged for the previous in-repo app API/token apps. Sibling HTMX apps should use `@getstrata/bootstrap` `CookieSessionStore` + `createCookieSessionAuthManager`.
286
+ HMAC `SessionGuard` is unchanged for HiroApp API/token apps. Sibling HTMX apps should use `@getstrata/bootstrap` `CookieSessionStore` + `createCookieSessionAuthManager`.
package/README.md CHANGED
@@ -1,71 +1,64 @@
1
1
  # @getstrata/core
2
2
 
3
- Stable **Strata** framework surface for application modules.
3
+ Runtime library for [Strata](https://github.com/EyK-26/strata) apps: HTTP, auth, database, queue, mail, and security. Requires Bun (tested on 1.4.x).
4
4
 
5
- **Source:** `src/framework/public-api.ts` (monorepo)
6
- **Repository:** [EyK-26/strata](https://github.com/EyK-26/strata), directory `packages/strata-core`
5
+ Generate an app rather than wiring this by hand:
7
6
 
8
- HiroApp is the example product. Import this package from your own modules. HTML cookie apps: [docs/BUILDING-APPS.md](../../docs/BUILDING-APPS.md). Auth choices: [docs/AUTH.md](../../docs/AUTH.md). SQL engines: [docs/DATABASE.md](../../docs/DATABASE.md).
7
+ ```bash
8
+ bunx create-strata my-app
9
+ ```
9
10
 
10
- ## Usage
11
+ ## Import subpaths, not the root
11
12
 
12
- Set `DATABASE_URL` before importing (the connection is created lazily on first query):
13
+ Apps, and anything they import, should use subpaths:
13
14
 
14
15
  ```typescript
15
- process.env.DATABASE_URL ??= "postgresql://postgres:postgres@localhost:5432/myapp";
16
-
17
- import { Policy } from "@getstrata/core/auth/policy";
16
+ import { createAuthMiddleware } from "@getstrata/core/http/authMiddleware";
17
+ import { bindDatabaseConnection } from "@getstrata/core/database/bindConnection";
18
18
  import { BaseRepository } from "@getstrata/core/database/baseRepository";
19
19
  import { FormRequest } from "@getstrata/core/http/formRequest";
20
+ import { Policy } from "@getstrata/core/auth/policy";
20
21
  import { withErrorHandling } from "@getstrata/core/http/response";
21
- import { EtaViewEngine } from "@getstrata/core/view";
22
+ import { ValidationError } from "@getstrata/core/errors/http";
23
+ import type { Migration } from "@getstrata/core/database/migrations/types";
22
24
  ```
23
25
 
24
- `.eta` files are **HTML + Eta tags** (`<% %>`, `<%= %>`, `<%~ include() %>`), not another template language. Class/attribute shorthand such as `section.section` or `a href=` fails at render time with the template name.
25
-
26
- **Dependency:** `eta` is a direct dependency of `@getstrata/core`. Apps do not need to list it separately. The database **engine** is your choice. Generated HiroApp uses **Bun's built-in `Bun.sql`** (Postgres): `createBunSqlPool()`, then `registerDefaultDatabasePool()` and `bindDatabaseConnection()`. `bindBunSql()` is a helper that registers both. Extra engines register with `registerNamedConnection` (`database/namedConnections`, `database/sqliteConnection`, `database/mysqlConnection`).
27
-
28
- `orderBy` accepts `{ column, direction }` objects or column shorthand such as `{ published_at: "desc" }`. `{ ilike }` uses the value as-is. Pass `%term%` yourself.
26
+ The root `@getstrata/core` import resolves, but nothing stops you from ending up with two copies of process-wide state: the database pool, the dialect override, async-local auth and tenant context, and the `HttpError` base class used with `instanceof`. Some subpaths re-export the main bundle for exactly that reason. Prefer subpaths everywhere and the problem does not arise.
29
27
 
30
- ## Admin and queue helpers
28
+ ## Database
31
29
 
32
- - `AdminResourceRegistry`, `formatAdminValue`: read-only resource browsers
33
- - `createFailedJobService`, `FailedJobService.delete()`: failed job persistence and cleanup
34
- - `runQueueJob`, `jobRegistry`: dispatch retried jobs from admin UIs
30
+ Set `DATABASE_URL` before the first query; the connection is created lazily.
35
31
 
36
- Core ships failed-job helpers and an optional admin resource registry. Generated HiroApp does not include an admin dashboard. You do not have to use the registry.
32
+ ```typescript
33
+ process.env.DATABASE_URL ??= "postgresql://postgres:postgres@localhost:5432/myapp";
34
+ ```
37
35
 
38
- ## Build and verify (monorepo root)
36
+ The engine is your choice, and an app should have one primary engine. Postgres apps use Bun's built-in `Bun.sql` through `createBunSqlPool()`, then `registerDefaultDatabasePool()` and `bindDatabaseConnection()`; `bindBunSql()` registers both. SQLite and MySQL register through `database/sqliteConnection` and `database/mysqlConnection`, or `registerNamedConnection` from `database/namedConnections`.
39
37
 
40
- ```bash
41
- bun run build:framework
42
- bun run verify:framework
43
- bun run verify:shared-subpaths
44
- ```
38
+ `orderBy` takes `{ column, direction }` objects or column shorthand such as `{ published_at: "desc" }`. `{ ilike }` uses the value as-is, so pass `%term%` yourself.
45
39
 
46
- ## Subpath imports
40
+ Full-text `tsMatch` is PostgreSQL only.
47
41
 
48
- `@getstrata/core` publishes many subpaths (for example `@getstrata/core/http/authMiddleware`, `@getstrata/core/database/migrations`). Prefer subpaths over the root import in apps, bootstrap, and tests.
42
+ `mysql2` and `eta` are direct dependencies, so they install even for a SQLite JSON API app.
49
43
 
50
- Some subpaths **re-export the main bundle** so singleton state stays shared (database pool, dialect override, async-local auth/tenant, `HttpError` for `instanceof`). The list lives in `scripts/core-shared-subpaths.ts`.
44
+ ## Views
51
45
 
52
- When adding a subpath that owns process-wide state or base classes used with `instanceof`, append it to `CORE_SHARED_SUBPATHS`, run `bun scripts/sync-package-subpaths.ts`, and rebuild.
46
+ `.eta` files are HTML plus Eta tags (`<% %>`, `<%= %>`, `<%~ include() %>`). Pug-style class or id shorthand such as `section.section` throws at render time and names the offending template.
53
47
 
54
- `scripts/verify-no-root-imports.ts` blocks root `@getstrata/core` imports in application source.
48
+ ## Admin and queue helpers
55
49
 
56
- ```typescript
57
- import { createAuthMiddleware } from "@getstrata/core/http/authMiddleware";
58
- import { bindDatabaseConnection } from "@getstrata/core/database/bindConnection";
59
- import { ValidationError } from "@getstrata/core/errors/http";
60
- import type { Migration } from "@getstrata/core/database/migrations/types";
61
- ```
50
+ - `AdminResourceRegistry`, `formatAdminValue`: read-only resource browsers
51
+ - `createFailedJobService`, `FailedJobService.delete()`: failed job persistence and cleanup
52
+ - `runQueueJob`, `jobRegistry`: dispatch retried jobs from admin UIs
62
53
 
63
- ## Publish to npm
54
+ Generated apps do not include an admin dashboard, and the registry is optional.
64
55
 
65
- Package name: **`@getstrata/core`** (npm org [`@getstrata`](https://www.npmjs.com/org/getstrata)).
56
+ ## Docs
66
57
 
67
- 1. Add `NPM_TOKEN` to GitHub repository secrets.
68
- 2. Tag a release: `git tag v1.0.0 && git push origin v1.0.0`
69
- 3. [Release workflow](../../.github/workflows/release.yml) builds and runs `npm publish --access public`.
58
+ - [Getting started](https://github.com/EyK-26/strata/blob/main/docs/GETTING-STARTED.md)
59
+ - [Building apps](https://github.com/EyK-26/strata/blob/main/docs/BUILDING-APPS.md)
60
+ - [Auth choices](https://github.com/EyK-26/strata/blob/main/docs/AUTH.md)
61
+ - [Databases](https://github.com/EyK-26/strata/blob/main/docs/DATABASE.md)
62
+ - [Production](https://github.com/EyK-26/strata/blob/main/docs/PRODUCTION.md)
70
63
 
71
- See [docs/PACKAGING.md](../../docs/PACKAGING.md).
64
+ Contributing to the framework itself: [CONTRIBUTING.md](https://github.com/EyK-26/strata/blob/main/CONTRIBUTING.md).
@@ -10,7 +10,8 @@ type ExtendedQueryOptions<TEntity extends object> = QueryOptions<TEntity> & {
10
10
  interface DatabaseConnection {
11
11
  unsafe<T>(query: string, params?: readonly unknown[]): Promise<T[]>;
12
12
  begin?<T>(callback: (transaction: DatabaseConnection) => Promise<T>): Promise<T>;
13
- close?(): Promise<void>;
13
+ /** SQLite closes synchronously; pooled drivers return a promise. */
14
+ close?(): void | Promise<void>;
14
15
  }
15
16
  interface SqlDatabaseConnection extends DatabaseConnection {
16
17
  (strings: TemplateStringsArray, ...values: unknown[]): Promise<unknown[]>;
@@ -4,6 +4,8 @@ interface SqlDialect {
4
4
  placeholder(index: number): string;
5
5
  quoteIdentifier(identifier: string): string;
6
6
  nowExpression(): string;
7
+ /** Render a Date as a literal this engine accepts for a timestamp column. */
8
+ timestampValue(value: Date): string;
7
9
  returningClause(columns: string): string;
8
10
  ilikeOperator(): "ILIKE" | "LIKE";
9
11
  nullsLastSuffix(): string;
@@ -12,7 +14,9 @@ interface SqlDialect {
12
14
  declare function dialectFor(driver: DatabaseDriver): SqlDialect;
13
15
  declare function currentSqlDialect(): SqlDialect;
14
16
  declare function useSqlDialect(driver: DatabaseDriver): SqlDialect;
17
+ /** Format a timestamp parameter for whichever engine is active. */
18
+ declare function sqlTimestamp(value?: Date): string;
15
19
  declare function resetSqlDialect(): void;
16
20
  declare function runWithSqlDialect<T>(driver: DatabaseDriver, callback: () => T | Promise<T>): T | Promise<T>;
17
21
  export type { SqlDialect };
18
- export { currentSqlDialect, dialectFor, resetSqlDialect, runWithSqlDialect, useSqlDialect };
22
+ export { currentSqlDialect, dialectFor, resetSqlDialect, runWithSqlDialect, sqlTimestamp, useSqlDialect, };
@@ -7,6 +7,10 @@ interface PostgresErrorLike {
7
7
  message?: string;
8
8
  }
9
9
  declare function isPostgresError(error: unknown): error is PostgresErrorLike;
10
+ /**
11
+ * Constraint violations become 4xx with a fixed message. Anything else is a
12
+ * 500 with a generic message; the raw driver text never reaches the client.
13
+ */
10
14
  declare function mapDatabaseError(error: unknown): HttpError;
11
15
  declare function withDatabaseErrorHandling<TValue>(operation: () => Promise<TValue>): Promise<TValue>;
12
16
  export { isPostgresError, mapDatabaseError, withDatabaseErrorHandling };
@@ -1,3 +1,4 @@
1
+ import mysql from "mysql2/promise";
1
2
  import type { ActiveDatabaseHandle } from "./connectionContext.ts";
2
3
  type MysqlExecutable = {
3
4
  execute: (sql: string, params?: unknown[]) => Promise<[unknown, unknown]>;
@@ -7,6 +8,7 @@ type MysqlConnection = ActiveDatabaseHandle & {
7
8
  close(): Promise<void>;
8
9
  };
9
10
  declare function createMysqlConnectionFromPool(pool: MysqlExecutable): MysqlConnection;
11
+ declare function createMysqlPool(url: string): mysql.Pool;
10
12
  declare function createMysqlConnection(url: string): MysqlConnection;
11
13
  export type { MysqlConnection, MysqlExecutable };
12
- export { createMysqlConnection, createMysqlConnectionFromPool };
14
+ export { createMysqlConnection, createMysqlConnectionFromPool, createMysqlPool };
@@ -30,6 +30,9 @@ declare class PayloadTooLargeError extends HttpError {
30
30
  declare class PreconditionFailedError extends HttpError {
31
31
  constructor(message?: string, details?: unknown);
32
32
  }
33
+ declare class InternalServerError extends HttpError {
34
+ constructor(message?: string, details?: unknown);
35
+ }
33
36
  interface HttpErrorLike {
34
37
  status: number;
35
38
  message: string;
@@ -37,4 +40,4 @@ interface HttpErrorLike {
37
40
  }
38
41
  declare function isHttpErrorLike(error: unknown): error is HttpErrorLike;
39
42
  declare function toHttpError(error: unknown): HttpError | null;
40
- export { BadRequestError, ConflictError, ForbiddenError, HttpError, isHttpErrorLike, NotFoundError, PayloadTooLargeError, PreconditionFailedError, toHttpError, UnauthorizedError, UnprocessableEntityError, ValidationError, };
43
+ export { BadRequestError, ConflictError, ForbiddenError, HttpError, InternalServerError, isHttpErrorLike, NotFoundError, PayloadTooLargeError, PreconditionFailedError, toHttpError, UnauthorizedError, UnprocessableEntityError, ValidationError, };
@@ -1,3 +1,6 @@
1
- declare function trustForwardedFor(env?: Record<string, string | undefined>): boolean;
2
- declare function readClientIp(request: Request, env?: Record<string, string | undefined>): string | undefined;
3
- export { readClientIp, trustForwardedFor };
1
+ type EnvRecord = Record<string, string | undefined>;
2
+ declare function trustForwardedFor(env?: EnvRecord): boolean;
3
+ declare function isPrivateAddress(address: string): boolean;
4
+ /** Forwarded headers only when trusted; otherwise the socket address the server recorded. */
5
+ declare function readClientIp(request: Request, env?: EnvRecord): string | undefined;
6
+ export { isPrivateAddress, readClientIp, trustForwardedFor };
@@ -1,5 +1,7 @@
1
+ import { type HttpError } from "@getstrata/core/errors/http";
1
2
  type FieldErrors = Record<string, string[]>;
2
3
  declare function normalizeFieldErrors(details: unknown): FieldErrors;
4
+ declare function logServerError(error: unknown, mappedError: HttpError): void;
3
5
  declare function webErrorResponse(error: unknown, request?: Request): Promise<Response | null>;
4
6
  export type { FieldErrors };
5
- export { normalizeFieldErrors, webErrorResponse };
7
+ export { logServerError, normalizeFieldErrors, webErrorResponse };
@@ -0,0 +1,4 @@
1
+ type EnvRecord = Record<string, string | undefined>;
2
+ /** Production when either the framework or the Node convention says so. */
3
+ declare function isProductionEnv(env?: EnvRecord): boolean;
4
+ export { isProductionEnv };
@@ -1,4 +1,5 @@
1
1
  type TenancyDriver = "rls" | "column" | "none";
2
+ /** Unset means rls (the Postgres example). Anything else must be an exact driver name. */
2
3
  declare function readTenancyDriver(env?: Record<string, string | undefined>): TenancyDriver;
3
4
  declare function isTenancyEnabled(env?: Record<string, string | undefined>): boolean;
4
5
  /** Postgres `SET LOCAL` / `set_config` for row-level security. SQLite and MySQL cannot do this. */
@@ -191,14 +191,16 @@ async function safeFetch(input, init = {}, options = {}) {
191
191
  import { repositoryConnection as db } from "@getstrata/core/database/repositoryConnection";
192
192
 
193
193
  // ../../src/core/tenant/tenancyConfig.ts
194
+ var TENANCY_DRIVERS = ["rls", "column", "none"];
194
195
  function readTenancyDriver(env = process.env) {
195
- if (env.TENANCY_DRIVER === "none") {
196
- return "none";
196
+ const raw = env.TENANCY_DRIVER?.trim();
197
+ if (raw === undefined || raw === "") {
198
+ return "rls";
197
199
  }
198
- if (env.TENANCY_DRIVER === "column") {
199
- return "column";
200
+ if (TENANCY_DRIVERS.includes(raw)) {
201
+ return raw;
200
202
  }
201
- return "rls";
203
+ throw new Error(`TENANCY_DRIVER must be one of ${TENANCY_DRIVERS.join(", ")}; received "${raw}".`);
202
204
  }
203
205
  function isTenancyEnabled(env = process.env) {
204
206
  return readTenancyDriver(env) !== "none";
@@ -78,14 +78,16 @@ function resolveScimTenantFromToken(token) {
78
78
  import { repositoryConnection as db } from "@getstrata/core/database/repositoryConnection";
79
79
 
80
80
  // ../../src/core/tenant/tenancyConfig.ts
81
+ var TENANCY_DRIVERS = ["rls", "column", "none"];
81
82
  function readTenancyDriver(env = process.env) {
82
- if (env.TENANCY_DRIVER === "none") {
83
- return "none";
83
+ const raw = env.TENANCY_DRIVER?.trim();
84
+ if (raw === undefined || raw === "") {
85
+ return "rls";
84
86
  }
85
- if (env.TENANCY_DRIVER === "column") {
86
- return "column";
87
+ if (TENANCY_DRIVERS.includes(raw)) {
88
+ return raw;
87
89
  }
88
- return "rls";
90
+ throw new Error(`TENANCY_DRIVER must be one of ${TENANCY_DRIVERS.join(", ")}; received "${raw}".`);
89
91
  }
90
92
  function isTenancyEnabled(env = process.env) {
91
93
  return readTenancyDriver(env) !== "none";
@@ -0,0 +1 @@
1
+ // @bun
@@ -3,6 +3,7 @@
3
3
  import {
4
4
  BadRequestError,
5
5
  ConflictError,
6
+ InternalServerError,
6
7
  toHttpError,
7
8
  UnprocessableEntityError
8
9
  } from "@getstrata/core/errors/http";
@@ -21,14 +22,55 @@ function getPostgresSqlState(error) {
21
22
  }
22
23
  return;
23
24
  }
25
+ var MYSQL_ERRNO_MESSAGES = {
26
+ 1062: () => new ConflictError("A record with these values already exists."),
27
+ 1451: () => new UnprocessableEntityError("Record is still referenced by other records."),
28
+ 1452: () => new UnprocessableEntityError("Referenced record does not exist."),
29
+ 1048: () => new BadRequestError("Required field is missing."),
30
+ 3819: () => new BadRequestError("Value violates a database constraint.")
31
+ };
32
+ function mapSqliteError(error) {
33
+ const code = typeof error.code === "string" ? error.code : "";
34
+ if (!code.startsWith("SQLITE_")) {
35
+ return null;
36
+ }
37
+ if (code === "SQLITE_CONSTRAINT_UNIQUE" || code === "SQLITE_CONSTRAINT_PRIMARYKEY") {
38
+ return new ConflictError("A record with these values already exists.");
39
+ }
40
+ if (code === "SQLITE_CONSTRAINT_FOREIGNKEY") {
41
+ return new UnprocessableEntityError("Referenced record does not exist.");
42
+ }
43
+ if (code === "SQLITE_CONSTRAINT_NOTNULL") {
44
+ return new BadRequestError("Required field is missing.");
45
+ }
46
+ if (code.startsWith("SQLITE_CONSTRAINT")) {
47
+ return new BadRequestError("Value violates a database constraint.");
48
+ }
49
+ return new InternalServerError("Database operation failed.");
50
+ }
51
+ function mapMysqlError(error) {
52
+ const code = typeof error.code === "string" ? error.code : "";
53
+ if (!code.startsWith("ER_")) {
54
+ return null;
55
+ }
56
+ const factory = typeof error.errno === "number" ? MYSQL_ERRNO_MESSAGES[error.errno] : undefined;
57
+ return factory ? factory() : new InternalServerError("Database operation failed.");
58
+ }
24
59
  function mapDatabaseError(error) {
25
60
  const httpError = toHttpError(error);
26
61
  if (httpError) {
27
62
  return httpError;
28
63
  }
29
64
  if (!isPostgresError(error)) {
30
- const message = error instanceof Error ? error.message : "Database operation failed.";
31
- return new BadRequestError(message);
65
+ return new InternalServerError;
66
+ }
67
+ const sqlite = mapSqliteError(error);
68
+ if (sqlite) {
69
+ return sqlite;
70
+ }
71
+ const mysql = mapMysqlError(error);
72
+ if (mysql) {
73
+ return mysql;
32
74
  }
33
75
  const sqlState = getPostgresSqlState(error);
34
76
  switch (sqlState) {
@@ -49,10 +91,7 @@ function mapDatabaseError(error) {
49
91
  constraint: error.constraint
50
92
  });
51
93
  default:
52
- return new BadRequestError(error.message ?? "Database operation failed.", {
53
- code: error.code,
54
- sqlState
55
- });
94
+ return new InternalServerError("Database operation failed.");
56
95
  }
57
96
  }
58
97
  async function withDatabaseErrorHandling(operation) {
@@ -23,13 +23,29 @@ function createMysqlConnectionFromPool(pool) {
23
23
  }
24
24
  };
25
25
  }
26
+ var MYSQL_SESSION_UTC = "SET time_zone = '+00:00'";
27
+ function pinSessionToUtc(connection) {
28
+ connection.query(MYSQL_SESSION_UTC, (error) => {
29
+ if (error) {
30
+ console.warn(`[mysql] Could not set the session time zone to UTC; DATETIME comparisons may drift: ${error instanceof Error ? error.message : String(error)}`);
31
+ }
32
+ });
33
+ }
34
+ function createMysqlPool(url) {
35
+ const pool = mysql.createPool({ uri: url, timezone: "Z" });
36
+ pool.on("connection", (connection) => {
37
+ pinSessionToUtc(connection);
38
+ });
39
+ return pool;
40
+ }
26
41
  function createMysqlConnection(url) {
27
42
  if (!url.trim()) {
28
43
  throw new Error("MYSQL_URL is not configured. Set url before creating a MySQL pool.");
29
44
  }
30
- return createMysqlConnectionFromPool(mysql.createPool(url));
45
+ return createMysqlConnectionFromPool(createMysqlPool(url));
31
46
  }
32
47
  export {
33
48
  createMysqlConnection,
34
- createMysqlConnectionFromPool
49
+ createMysqlConnectionFromPool,
50
+ createMysqlPool
35
51
  };
@@ -14,6 +14,11 @@ function createSqliteConnection(filename) {
14
14
  }
15
15
  const db = new Database(filename, { create: true });
16
16
  db.exec("PRAGMA foreign_keys = ON");
17
+ db.exec("PRAGMA busy_timeout = 5000");
18
+ if (filename !== ":memory:") {
19
+ db.exec("PRAGMA journal_mode = WAL");
20
+ db.exec("PRAGMA synchronous = NORMAL");
21
+ }
17
22
  return {
18
23
  async unsafe(query, params = []) {
19
24
  const statement = db.query(query);