@getstrata/core 1.0.1 → 1.0.3
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/CHANGELOG.md +11 -0
- package/README.md +1 -1
- package/dist/core/database/index.d.ts +2 -1
- package/dist/core/database/mysqlConnection.d.ts +8 -4
- package/dist/core/runtime/optionalPeer.d.ts +2 -0
- package/dist/core/view/etaViewEngine.d.ts +5 -1
- package/dist/entries/database/mysqlConnection.js +86 -5
- package/dist/entries/view.js +41 -11
- package/dist/framework/public-api.d.ts +1 -0
- package/dist/index.js +113 -15
- package/package.json +11 -5
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# @getstrata/core changelog
|
|
2
2
|
|
|
3
|
+
## 1.0.3
|
|
4
|
+
|
|
5
|
+
- `eta` and `mysql2` are optional peers, loaded with `import()` on first use. SQLite and Postgres apps no longer install `mysql2` through core. If a package is missing, the error names `bun add` for that package.
|
|
6
|
+
- Breaking: `createMysqlPool(url)` returns a `Promise` because it lazy-loads `mysql2`. It shipped synchronous in 1.0.1 and 1.0.2; `await` it. `createMysqlConnection(url)` stays synchronous and opens the pool on the first query.
|
|
7
|
+
- `createMysqlConnection()` shares one pool across concurrent first queries by caching the pending promise. Before this fix, three requests arriving together opened three pools and `close()` ended only the last one. A failed load is retried on the next query, and a query after `close()` opens a fresh pool.
|
|
8
|
+
- The MySQL helpers stay exported from `@getstrata/core` and `@getstrata/core/database`. Importing either barrel does not install `mysql2`; the barrel bundle carries the lazy `import("mysql2/promise")` and evaluates it only when a MySQL pool is opened.
|
|
9
|
+
|
|
10
|
+
## 1.0.2
|
|
11
|
+
|
|
12
|
+
- Lockstep with `create-strata` 1.0.2. No runtime changes.
|
|
13
|
+
|
|
3
14
|
## 1.0.1
|
|
4
15
|
|
|
5
16
|
- Publish the `contracts/authUserDirectory` subpath. Generated cookie and token apps import `AuthUserDirectory` from it, and without the export their `tsc --noEmit` failed with TS2307.
|
package/README.md
CHANGED
|
@@ -39,7 +39,7 @@ The engine is your choice, and an app should have one primary engine. Postgres a
|
|
|
39
39
|
|
|
40
40
|
Full-text `tsMatch` is PostgreSQL only.
|
|
41
41
|
|
|
42
|
-
`mysql2` and `eta` are
|
|
42
|
+
`eta` and `mysql2` are optional peers. Core loads them with `import()` the first time you render a view or open a MySQL connection. SQLite and Postgres apps do not install `mysql2`. Generated apps add `eta` because the welcome page uses it. MySQL apps also add `mysql2`. The MySQL helpers are exported from `@getstrata/core/database/mysqlConnection`, `@getstrata/core/database`, and the root barrel; importing any of them does not install `mysql2`, and apps should prefer the subpath as with every other core module.
|
|
43
43
|
|
|
44
44
|
## Views
|
|
45
45
|
|
|
@@ -10,7 +10,8 @@ export { Factory } from "./factory.ts";
|
|
|
10
10
|
export { foreignKeyFromTable, pivotTableName, singularize } from "./inflection.ts";
|
|
11
11
|
export type { CastType, GlobalScopeFn, ModelConstructor } from "./model.ts";
|
|
12
12
|
export { applyCasts, BelongsToManyRelationQuery, BelongsToRelationQuery, dehydrateValue, filterMassAssignable, HasManyRelationQuery, HasManyThroughRelationQuery, HasOneRelationQuery, hydrateValue, Model, ModelQuery, MorphManyRelationQuery, MorphOneRelationQuery, MorphToRelationQuery, registerModelClass, registerModelRepository, } from "./model.ts";
|
|
13
|
-
export {
|
|
13
|
+
export type { MysqlConnection, MysqlExecutable, MysqlPool } from "./mysqlConnection.ts";
|
|
14
|
+
export { createMysqlConnection, createMysqlConnectionFromPool, createMysqlPool, } from "./mysqlConnection.ts";
|
|
14
15
|
export type { NamedConnectionEntry } from "./namedConnections.ts";
|
|
15
16
|
export { getNamedConnection, hasNamedConnection, registerNamedConnection, resetNamedConnections, runOnNamedConnection, unregisterNamedConnection, } from "./namedConnections.ts";
|
|
16
17
|
export { buildAdvancedWhereClause, buildCountQuery, buildDeleteByIdQuery, buildGroupedCountQuery, buildInsertQuery, buildJoinClause, buildOrderByClause, buildProjectionQuery, buildQueryWhereClause, buildRestoreByIdQuery, buildSelectQuery, buildSoftDeleteByIdQuery, buildUpdateQuery, buildWhereClause, parseQualifiedColumn, qualifyColumn, quoteIdentifier, resolveQualifiedColumn, resolveSoftDeleteColumn, } from "./query.ts";
|
|
@@ -1,14 +1,18 @@
|
|
|
1
|
-
import mysql from "mysql2/promise";
|
|
2
1
|
import type { ActiveDatabaseHandle } from "./connectionContext.ts";
|
|
3
2
|
type MysqlExecutable = {
|
|
4
3
|
execute: (sql: string, params?: unknown[]) => Promise<[unknown, unknown]>;
|
|
5
4
|
end?: () => Promise<void>;
|
|
6
5
|
};
|
|
6
|
+
type MysqlPool = MysqlExecutable & {
|
|
7
|
+
end(): Promise<void>;
|
|
8
|
+
on(event: "connection", listener: (connection: unknown) => void): void;
|
|
9
|
+
};
|
|
7
10
|
type MysqlConnection = ActiveDatabaseHandle & {
|
|
8
11
|
close(): Promise<void>;
|
|
9
12
|
};
|
|
13
|
+
declare function resetMysqlLoaderForTests(importer?: () => Promise<unknown>): void;
|
|
10
14
|
declare function createMysqlConnectionFromPool(pool: MysqlExecutable): MysqlConnection;
|
|
11
|
-
declare function createMysqlPool(url: string):
|
|
15
|
+
declare function createMysqlPool(url: string): Promise<MysqlPool>;
|
|
12
16
|
declare function createMysqlConnection(url: string): MysqlConnection;
|
|
13
|
-
export type { MysqlConnection, MysqlExecutable };
|
|
14
|
-
export { createMysqlConnection, createMysqlConnectionFromPool, createMysqlPool };
|
|
17
|
+
export type { MysqlConnection, MysqlExecutable, MysqlPool };
|
|
18
|
+
export { createMysqlConnection, createMysqlConnectionFromPool, createMysqlPool, resetMysqlLoaderForTests, };
|
|
@@ -11,9 +11,13 @@ type LayoutDataResolver = (request?: Request) => Promise<Record<string, unknown>
|
|
|
11
11
|
* Pug class/attribute shorthand is rejected at render time.
|
|
12
12
|
*/
|
|
13
13
|
declare class EtaViewEngine implements ViewEngine {
|
|
14
|
-
private
|
|
14
|
+
private eta;
|
|
15
|
+
private etaPending;
|
|
16
|
+
private readonly viewsDirectory;
|
|
15
17
|
private readonly resolveLayoutData?;
|
|
16
18
|
constructor(viewsDirectory?: string, resolveLayoutData?: LayoutDataResolver);
|
|
19
|
+
private getEta;
|
|
20
|
+
private createEta;
|
|
17
21
|
render(name: string, data?: Record<string, unknown>, options?: RenderOptions): Promise<string>;
|
|
18
22
|
}
|
|
19
23
|
export type { LayoutDataResolver, RenderOptions };
|
|
@@ -1,6 +1,58 @@
|
|
|
1
1
|
// @bun
|
|
2
|
+
// ../../src/core/runtime/optionalPeer.ts
|
|
3
|
+
function missingOptionalPeer(packageName, reason, error) {
|
|
4
|
+
return new Error(`Install ${packageName} ${reason} (\`bun add ${packageName}\`).`, {
|
|
5
|
+
cause: error
|
|
6
|
+
});
|
|
7
|
+
}
|
|
8
|
+
|
|
2
9
|
// ../../src/core/database/mysqlConnection.ts
|
|
3
|
-
|
|
10
|
+
var MYSQL_SESSION_UTC = "SET time_zone = '+00:00'";
|
|
11
|
+
var mysqlModule;
|
|
12
|
+
var mysqlPending;
|
|
13
|
+
var importMysql = defaultImportMysql;
|
|
14
|
+
async function defaultImportMysql() {
|
|
15
|
+
return import("mysql2/promise");
|
|
16
|
+
}
|
|
17
|
+
function resetMysqlLoaderForTests(importer) {
|
|
18
|
+
mysqlModule = undefined;
|
|
19
|
+
mysqlPending = undefined;
|
|
20
|
+
importMysql = importer ? async () => await importer() : defaultImportMysql;
|
|
21
|
+
}
|
|
22
|
+
function mysqlApi(mod) {
|
|
23
|
+
if (typeof mod.createPool === "function") {
|
|
24
|
+
return mod;
|
|
25
|
+
}
|
|
26
|
+
const withDefault = mod;
|
|
27
|
+
if (typeof withDefault.default?.createPool === "function") {
|
|
28
|
+
return withDefault.default;
|
|
29
|
+
}
|
|
30
|
+
throw new Error("mysql2/promise did not export createPool.");
|
|
31
|
+
}
|
|
32
|
+
async function loadMysql() {
|
|
33
|
+
if (mysqlModule) {
|
|
34
|
+
return mysqlModule;
|
|
35
|
+
}
|
|
36
|
+
if (!mysqlPending) {
|
|
37
|
+
mysqlPending = (async () => {
|
|
38
|
+
let mod;
|
|
39
|
+
try {
|
|
40
|
+
mod = await importMysql();
|
|
41
|
+
} catch (error) {
|
|
42
|
+
mysqlPending = undefined;
|
|
43
|
+
throw missingOptionalPeer("mysql2", "to open a MySQL connection", error);
|
|
44
|
+
}
|
|
45
|
+
try {
|
|
46
|
+
mysqlModule = mysqlApi(mod);
|
|
47
|
+
return mysqlModule;
|
|
48
|
+
} catch (error) {
|
|
49
|
+
mysqlPending = undefined;
|
|
50
|
+
throw error;
|
|
51
|
+
}
|
|
52
|
+
})();
|
|
53
|
+
}
|
|
54
|
+
return mysqlPending;
|
|
55
|
+
}
|
|
4
56
|
function rowsFromResult(result) {
|
|
5
57
|
if (Array.isArray(result)) {
|
|
6
58
|
return result;
|
|
@@ -23,7 +75,6 @@ function createMysqlConnectionFromPool(pool) {
|
|
|
23
75
|
}
|
|
24
76
|
};
|
|
25
77
|
}
|
|
26
|
-
var MYSQL_SESSION_UTC = "SET time_zone = '+00:00'";
|
|
27
78
|
function pinSessionToUtc(connection) {
|
|
28
79
|
connection.query(MYSQL_SESSION_UTC, (error) => {
|
|
29
80
|
if (error) {
|
|
@@ -31,21 +82,51 @@ function pinSessionToUtc(connection) {
|
|
|
31
82
|
}
|
|
32
83
|
});
|
|
33
84
|
}
|
|
34
|
-
function
|
|
85
|
+
function createPoolFromModule(mysql, url) {
|
|
35
86
|
const pool = mysql.createPool({ uri: url, timezone: "Z" });
|
|
36
87
|
pool.on("connection", (connection) => {
|
|
37
88
|
pinSessionToUtc(connection);
|
|
38
89
|
});
|
|
39
90
|
return pool;
|
|
40
91
|
}
|
|
92
|
+
async function createMysqlPool(url) {
|
|
93
|
+
return createPoolFromModule(await loadMysql(), url);
|
|
94
|
+
}
|
|
41
95
|
function createMysqlConnection(url) {
|
|
42
96
|
if (!url.trim()) {
|
|
43
97
|
throw new Error("MYSQL_URL is not configured. Set url before creating a MySQL pool.");
|
|
44
98
|
}
|
|
45
|
-
|
|
99
|
+
let poolPending;
|
|
100
|
+
function ensurePool() {
|
|
101
|
+
if (!poolPending) {
|
|
102
|
+
poolPending = createMysqlPool(url).catch((error) => {
|
|
103
|
+
poolPending = undefined;
|
|
104
|
+
throw error;
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
return poolPending;
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
async unsafe(query, params = []) {
|
|
111
|
+
const [result] = await (await ensurePool()).execute(query, [...params]);
|
|
112
|
+
return rowsFromResult(result);
|
|
113
|
+
},
|
|
114
|
+
async close() {
|
|
115
|
+
if (!poolPending) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
const pending = poolPending;
|
|
119
|
+
poolPending = undefined;
|
|
120
|
+
const pool = await pending.catch(() => {
|
|
121
|
+
return;
|
|
122
|
+
});
|
|
123
|
+
await pool?.end();
|
|
124
|
+
}
|
|
125
|
+
};
|
|
46
126
|
}
|
|
47
127
|
export {
|
|
48
128
|
createMysqlConnection,
|
|
49
129
|
createMysqlConnectionFromPool,
|
|
50
|
-
createMysqlPool
|
|
130
|
+
createMysqlPool,
|
|
131
|
+
resetMysqlLoaderForTests
|
|
51
132
|
};
|
package/dist/entries/view.js
CHANGED
|
@@ -2,7 +2,13 @@
|
|
|
2
2
|
// ../../src/core/view/etaViewEngine.ts
|
|
3
3
|
import { join, relative } from "path";
|
|
4
4
|
import { currentRequestMeta } from "@getstrata/core/http/requestMetaContext";
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
// ../../src/core/runtime/optionalPeer.ts
|
|
7
|
+
function missingOptionalPeer(packageName, reason, error) {
|
|
8
|
+
return new Error(`Install ${packageName} ${reason} (\`bun add ${packageName}\`).`, {
|
|
9
|
+
cause: error
|
|
10
|
+
});
|
|
11
|
+
}
|
|
6
12
|
|
|
7
13
|
// ../../src/core/view/assertEtaHtmlSource.ts
|
|
8
14
|
var HTML_TAGS = new Set([
|
|
@@ -182,33 +188,57 @@ var DEFAULT_VIEWS_DIRECTORY = join(process.cwd(), "resources/views");
|
|
|
182
188
|
var DEFAULT_LAYOUT = "layouts/app.eta";
|
|
183
189
|
|
|
184
190
|
class EtaViewEngine {
|
|
185
|
-
eta;
|
|
191
|
+
eta = null;
|
|
192
|
+
etaPending = null;
|
|
193
|
+
viewsDirectory;
|
|
186
194
|
resolveLayoutData;
|
|
187
195
|
constructor(viewsDirectory = DEFAULT_VIEWS_DIRECTORY, resolveLayoutData) {
|
|
188
|
-
this.
|
|
189
|
-
views: viewsDirectory,
|
|
190
|
-
autoTrim: false
|
|
191
|
-
});
|
|
196
|
+
this.viewsDirectory = viewsDirectory;
|
|
192
197
|
this.resolveLayoutData = resolveLayoutData;
|
|
193
|
-
|
|
194
|
-
|
|
198
|
+
}
|
|
199
|
+
async getEta() {
|
|
200
|
+
if (this.eta) {
|
|
201
|
+
return this.eta;
|
|
202
|
+
}
|
|
203
|
+
if (!this.etaPending) {
|
|
204
|
+
this.etaPending = this.createEta();
|
|
205
|
+
}
|
|
206
|
+
return this.etaPending;
|
|
207
|
+
}
|
|
208
|
+
async createEta() {
|
|
209
|
+
let eta;
|
|
210
|
+
try {
|
|
211
|
+
const mod = await import("eta");
|
|
212
|
+
eta = new mod.Eta({
|
|
213
|
+
views: this.viewsDirectory,
|
|
214
|
+
autoTrim: false
|
|
215
|
+
});
|
|
216
|
+
} catch (error) {
|
|
217
|
+
this.etaPending = null;
|
|
218
|
+
throw missingOptionalPeer("eta", "to render HTML views", error);
|
|
219
|
+
}
|
|
220
|
+
const readFile = eta.readFile?.bind(eta);
|
|
221
|
+
eta.readFile = (path) => {
|
|
195
222
|
const source = readFile ? readFile(path) : "";
|
|
196
|
-
assertEtaHtmlSource(relative(viewsDirectory, path) || path, source);
|
|
223
|
+
assertEtaHtmlSource(relative(this.viewsDirectory, path) || path, source);
|
|
197
224
|
return source;
|
|
198
225
|
};
|
|
226
|
+
this.eta = eta;
|
|
227
|
+
return eta;
|
|
199
228
|
}
|
|
200
229
|
async render(name, data = {}, options = {}) {
|
|
201
230
|
const template = name.endsWith(".eta") ? name : `${name}.eta`;
|
|
202
231
|
const request = options.request ?? currentRequestMeta().request;
|
|
203
232
|
const layoutData = this.resolveLayoutData ? await this.resolveLayoutData(request) : {};
|
|
204
233
|
const mergedData = { ...layoutData, ...data };
|
|
205
|
-
const
|
|
234
|
+
const eta = await this.getEta();
|
|
235
|
+
const body = await eta.renderAsync(template, mergedData);
|
|
206
236
|
const layout = options.layout ?? DEFAULT_LAYOUT;
|
|
207
237
|
if (layout === false) {
|
|
208
238
|
return body;
|
|
209
239
|
}
|
|
210
240
|
const layoutTemplate = layout.endsWith(".eta") ? layout : `${layout}.eta`;
|
|
211
|
-
return await
|
|
241
|
+
return await eta.renderAsync(layoutTemplate, {
|
|
212
242
|
...mergedData,
|
|
213
243
|
body
|
|
214
244
|
});
|
|
@@ -43,6 +43,7 @@ export { freshDatabase, getMigrationStatus, loadMigrationsFromDirectory, migrate
|
|
|
43
43
|
export type { Migration, MigrationDatabase, MigrationStatus, } from "../core/database/migrations/types.ts";
|
|
44
44
|
export type { CastType, GlobalScopeFn, ModelConstructor } from "../core/database/model.ts";
|
|
45
45
|
export { applyCasts, BelongsToManyRelationQuery, BelongsToRelationQuery, dehydrateValue, filterMassAssignable, HasManyRelationQuery, HasOneRelationQuery, hydrateValue, Model, ModelQuery, MorphManyRelationQuery, MorphOneRelationQuery, MorphToRelationQuery, registerModelClass, registerModelRepository, } from "../core/database/model.ts";
|
|
46
|
+
export type { MysqlConnection, MysqlExecutable, MysqlPool, } from "../core/database/mysqlConnection.ts";
|
|
46
47
|
export { createMysqlConnection, createMysqlConnectionFromPool, createMysqlPool, } from "../core/database/mysqlConnection.ts";
|
|
47
48
|
export { getNamedConnection, hasNamedConnection, registerNamedConnection, resetNamedConnections, runOnNamedConnection, unregisterNamedConnection, } from "../core/database/namedConnections.ts";
|
|
48
49
|
export { createDatabaseQueryProxy } from "../core/database/queryProxy.ts";
|
package/dist/index.js
CHANGED
|
@@ -5211,8 +5211,55 @@ function registerModelRepository(model, repository) {
|
|
|
5211
5211
|
ensureBooted(model);
|
|
5212
5212
|
return model;
|
|
5213
5213
|
}
|
|
5214
|
+
// ../../src/core/runtime/optionalPeer.ts
|
|
5215
|
+
function missingOptionalPeer(packageName, reason, error) {
|
|
5216
|
+
return new Error(`Install ${packageName} ${reason} (\`bun add ${packageName}\`).`, {
|
|
5217
|
+
cause: error
|
|
5218
|
+
});
|
|
5219
|
+
}
|
|
5220
|
+
|
|
5214
5221
|
// ../../src/core/database/mysqlConnection.ts
|
|
5215
|
-
|
|
5222
|
+
var MYSQL_SESSION_UTC = "SET time_zone = '+00:00'";
|
|
5223
|
+
var mysqlModule;
|
|
5224
|
+
var mysqlPending;
|
|
5225
|
+
var importMysql = defaultImportMysql;
|
|
5226
|
+
async function defaultImportMysql() {
|
|
5227
|
+
return import("mysql2/promise");
|
|
5228
|
+
}
|
|
5229
|
+
function mysqlApi(mod) {
|
|
5230
|
+
if (typeof mod.createPool === "function") {
|
|
5231
|
+
return mod;
|
|
5232
|
+
}
|
|
5233
|
+
const withDefault = mod;
|
|
5234
|
+
if (typeof withDefault.default?.createPool === "function") {
|
|
5235
|
+
return withDefault.default;
|
|
5236
|
+
}
|
|
5237
|
+
throw new Error("mysql2/promise did not export createPool.");
|
|
5238
|
+
}
|
|
5239
|
+
async function loadMysql() {
|
|
5240
|
+
if (mysqlModule) {
|
|
5241
|
+
return mysqlModule;
|
|
5242
|
+
}
|
|
5243
|
+
if (!mysqlPending) {
|
|
5244
|
+
mysqlPending = (async () => {
|
|
5245
|
+
let mod;
|
|
5246
|
+
try {
|
|
5247
|
+
mod = await importMysql();
|
|
5248
|
+
} catch (error) {
|
|
5249
|
+
mysqlPending = undefined;
|
|
5250
|
+
throw missingOptionalPeer("mysql2", "to open a MySQL connection", error);
|
|
5251
|
+
}
|
|
5252
|
+
try {
|
|
5253
|
+
mysqlModule = mysqlApi(mod);
|
|
5254
|
+
return mysqlModule;
|
|
5255
|
+
} catch (error) {
|
|
5256
|
+
mysqlPending = undefined;
|
|
5257
|
+
throw error;
|
|
5258
|
+
}
|
|
5259
|
+
})();
|
|
5260
|
+
}
|
|
5261
|
+
return mysqlPending;
|
|
5262
|
+
}
|
|
5216
5263
|
function rowsFromResult(result) {
|
|
5217
5264
|
if (Array.isArray(result)) {
|
|
5218
5265
|
return result;
|
|
@@ -5235,7 +5282,6 @@ function createMysqlConnectionFromPool(pool) {
|
|
|
5235
5282
|
}
|
|
5236
5283
|
};
|
|
5237
5284
|
}
|
|
5238
|
-
var MYSQL_SESSION_UTC = "SET time_zone = '+00:00'";
|
|
5239
5285
|
function pinSessionToUtc(connection) {
|
|
5240
5286
|
connection.query(MYSQL_SESSION_UTC, (error) => {
|
|
5241
5287
|
if (error) {
|
|
@@ -5243,18 +5289,47 @@ function pinSessionToUtc(connection) {
|
|
|
5243
5289
|
}
|
|
5244
5290
|
});
|
|
5245
5291
|
}
|
|
5246
|
-
function
|
|
5292
|
+
function createPoolFromModule(mysql, url) {
|
|
5247
5293
|
const pool = mysql.createPool({ uri: url, timezone: "Z" });
|
|
5248
5294
|
pool.on("connection", (connection) => {
|
|
5249
5295
|
pinSessionToUtc(connection);
|
|
5250
5296
|
});
|
|
5251
5297
|
return pool;
|
|
5252
5298
|
}
|
|
5299
|
+
async function createMysqlPool(url) {
|
|
5300
|
+
return createPoolFromModule(await loadMysql(), url);
|
|
5301
|
+
}
|
|
5253
5302
|
function createMysqlConnection(url) {
|
|
5254
5303
|
if (!url.trim()) {
|
|
5255
5304
|
throw new Error("MYSQL_URL is not configured. Set url before creating a MySQL pool.");
|
|
5256
5305
|
}
|
|
5257
|
-
|
|
5306
|
+
let poolPending;
|
|
5307
|
+
function ensurePool() {
|
|
5308
|
+
if (!poolPending) {
|
|
5309
|
+
poolPending = createMysqlPool(url).catch((error) => {
|
|
5310
|
+
poolPending = undefined;
|
|
5311
|
+
throw error;
|
|
5312
|
+
});
|
|
5313
|
+
}
|
|
5314
|
+
return poolPending;
|
|
5315
|
+
}
|
|
5316
|
+
return {
|
|
5317
|
+
async unsafe(query, params = []) {
|
|
5318
|
+
const [result] = await (await ensurePool()).execute(query, [...params]);
|
|
5319
|
+
return rowsFromResult(result);
|
|
5320
|
+
},
|
|
5321
|
+
async close() {
|
|
5322
|
+
if (!poolPending) {
|
|
5323
|
+
return;
|
|
5324
|
+
}
|
|
5325
|
+
const pending = poolPending;
|
|
5326
|
+
poolPending = undefined;
|
|
5327
|
+
const pool = await pending.catch(() => {
|
|
5328
|
+
return;
|
|
5329
|
+
});
|
|
5330
|
+
await pool?.end();
|
|
5331
|
+
}
|
|
5332
|
+
};
|
|
5258
5333
|
}
|
|
5259
5334
|
// ../../src/core/database/namedConnections.ts
|
|
5260
5335
|
var REGISTRY_KEY = Symbol.for("@getstrata/namedConnections");
|
|
@@ -9229,7 +9304,6 @@ function validateObject(payload, schema) {
|
|
|
9229
9304
|
}
|
|
9230
9305
|
// ../../src/core/view/etaViewEngine.ts
|
|
9231
9306
|
import { join as join4, relative } from "path";
|
|
9232
|
-
import { Eta } from "eta";
|
|
9233
9307
|
|
|
9234
9308
|
// ../../src/core/view/assertEtaHtmlSource.ts
|
|
9235
9309
|
var HTML_TAGS = new Set([
|
|
@@ -9409,33 +9483,57 @@ var DEFAULT_VIEWS_DIRECTORY = join4(process.cwd(), "resources/views");
|
|
|
9409
9483
|
var DEFAULT_LAYOUT = "layouts/app.eta";
|
|
9410
9484
|
|
|
9411
9485
|
class EtaViewEngine {
|
|
9412
|
-
eta;
|
|
9486
|
+
eta = null;
|
|
9487
|
+
etaPending = null;
|
|
9488
|
+
viewsDirectory;
|
|
9413
9489
|
resolveLayoutData;
|
|
9414
9490
|
constructor(viewsDirectory = DEFAULT_VIEWS_DIRECTORY, resolveLayoutData) {
|
|
9415
|
-
this.
|
|
9416
|
-
views: viewsDirectory,
|
|
9417
|
-
autoTrim: false
|
|
9418
|
-
});
|
|
9491
|
+
this.viewsDirectory = viewsDirectory;
|
|
9419
9492
|
this.resolveLayoutData = resolveLayoutData;
|
|
9420
|
-
|
|
9421
|
-
|
|
9493
|
+
}
|
|
9494
|
+
async getEta() {
|
|
9495
|
+
if (this.eta) {
|
|
9496
|
+
return this.eta;
|
|
9497
|
+
}
|
|
9498
|
+
if (!this.etaPending) {
|
|
9499
|
+
this.etaPending = this.createEta();
|
|
9500
|
+
}
|
|
9501
|
+
return this.etaPending;
|
|
9502
|
+
}
|
|
9503
|
+
async createEta() {
|
|
9504
|
+
let eta;
|
|
9505
|
+
try {
|
|
9506
|
+
const mod = await import("eta");
|
|
9507
|
+
eta = new mod.Eta({
|
|
9508
|
+
views: this.viewsDirectory,
|
|
9509
|
+
autoTrim: false
|
|
9510
|
+
});
|
|
9511
|
+
} catch (error) {
|
|
9512
|
+
this.etaPending = null;
|
|
9513
|
+
throw missingOptionalPeer("eta", "to render HTML views", error);
|
|
9514
|
+
}
|
|
9515
|
+
const readFile = eta.readFile?.bind(eta);
|
|
9516
|
+
eta.readFile = (path) => {
|
|
9422
9517
|
const source = readFile ? readFile(path) : "";
|
|
9423
|
-
assertEtaHtmlSource(relative(viewsDirectory, path) || path, source);
|
|
9518
|
+
assertEtaHtmlSource(relative(this.viewsDirectory, path) || path, source);
|
|
9424
9519
|
return source;
|
|
9425
9520
|
};
|
|
9521
|
+
this.eta = eta;
|
|
9522
|
+
return eta;
|
|
9426
9523
|
}
|
|
9427
9524
|
async render(name, data = {}, options = {}) {
|
|
9428
9525
|
const template = name.endsWith(".eta") ? name : `${name}.eta`;
|
|
9429
9526
|
const request = options.request ?? currentRequestMeta().request;
|
|
9430
9527
|
const layoutData = this.resolveLayoutData ? await this.resolveLayoutData(request) : {};
|
|
9431
9528
|
const mergedData = { ...layoutData, ...data };
|
|
9432
|
-
const
|
|
9529
|
+
const eta = await this.getEta();
|
|
9530
|
+
const body = await eta.renderAsync(template, mergedData);
|
|
9433
9531
|
const layout = options.layout ?? DEFAULT_LAYOUT;
|
|
9434
9532
|
if (layout === false) {
|
|
9435
9533
|
return body;
|
|
9436
9534
|
}
|
|
9437
9535
|
const layoutTemplate = layout.endsWith(".eta") ? layout : `${layout}.eta`;
|
|
9438
|
-
return await
|
|
9536
|
+
return await eta.renderAsync(layoutTemplate, {
|
|
9439
9537
|
...mergedData,
|
|
9440
9538
|
body
|
|
9441
9539
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@getstrata/core",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Strata Bun framework public API",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -904,13 +904,19 @@
|
|
|
904
904
|
"publishConfig": {
|
|
905
905
|
"access": "public"
|
|
906
906
|
},
|
|
907
|
-
"dependencies": {
|
|
908
|
-
"eta": "^4.6.0",
|
|
909
|
-
"mysql2": "^3.24.3"
|
|
910
|
-
},
|
|
911
907
|
"peerDependencies": {
|
|
908
|
+
"eta": "^4.6.0",
|
|
909
|
+
"mysql2": "^3.24.3",
|
|
912
910
|
"typescript": "^5.9.0"
|
|
913
911
|
},
|
|
912
|
+
"peerDependenciesMeta": {
|
|
913
|
+
"eta": {
|
|
914
|
+
"optional": true
|
|
915
|
+
},
|
|
916
|
+
"mysql2": {
|
|
917
|
+
"optional": true
|
|
918
|
+
}
|
|
919
|
+
},
|
|
914
920
|
"engines": {
|
|
915
921
|
"bun": ">=1.4.0"
|
|
916
922
|
}
|