tina4-nodejs 3.2.1 → 3.5.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/CLAUDE.md +1 -1
- package/README.md +1 -1
- package/package.json +1 -1
- package/packages/cli/src/bin.ts +13 -1
- package/packages/cli/src/commands/migrate.ts +19 -5
- package/packages/cli/src/commands/migrateCreate.ts +29 -28
- package/packages/cli/src/commands/migrateRollback.ts +59 -0
- package/packages/cli/src/commands/migrateStatus.ts +62 -0
- package/packages/core/public/js/tina4-dev-admin.min.js +1 -1
- package/packages/core/public/js/tina4js.min.js +47 -0
- package/packages/core/src/auth.ts +44 -10
- package/packages/core/src/devAdmin.ts +14 -16
- package/packages/core/src/index.ts +10 -3
- package/packages/core/src/middleware.ts +232 -2
- package/packages/core/src/queue.ts +127 -25
- package/packages/core/src/queueBackends/mongoBackend.ts +223 -0
- package/packages/core/src/request.ts +3 -3
- package/packages/core/src/router.ts +115 -51
- package/packages/core/src/server.ts +47 -3
- package/packages/core/src/session.ts +29 -1
- package/packages/core/src/sessionHandlers/databaseHandler.ts +134 -0
- package/packages/core/src/sessionHandlers/redisHandler.ts +230 -0
- package/packages/core/src/types.ts +12 -6
- package/packages/core/src/websocket.ts +11 -2
- package/packages/core/src/websocketConnection.ts +4 -2
- package/packages/frond/src/engine.ts +66 -1
- package/packages/orm/src/autoCrud.ts +17 -12
- package/packages/orm/src/baseModel.ts +99 -21
- package/packages/orm/src/database.ts +197 -69
- package/packages/orm/src/databaseResult.ts +207 -0
- package/packages/orm/src/index.ts +6 -3
- package/packages/orm/src/migration.ts +296 -71
- package/packages/orm/src/model.ts +1 -0
- package/packages/orm/src/types.ts +1 -0
package/CLAUDE.md
CHANGED
|
@@ -587,7 +587,7 @@ When adding new features, add a corresponding `test/<feature>.test.ts` file.
|
|
|
587
587
|
- **`npx tina4nodejs generate`**: model, route, migration, middleware scaffolding
|
|
588
588
|
- **Database**: 5 engines (SQLite, PostgreSQL, MySQL, MSSQL, Firebird), query caching (`TINA4_DB_CACHE=true`)
|
|
589
589
|
- **Sessions**: file backend (default)
|
|
590
|
-
- **Queue**: SQLite/RabbitMQ/Kafka backends, configured via env vars
|
|
590
|
+
- **Queue**: SQLite/RabbitMQ/Kafka/MongoDB backends, configured via env vars
|
|
591
591
|
- **Cache**: memory/Redis/file backends
|
|
592
592
|
- **Messenger**: .env driven SMTP/IMAP
|
|
593
593
|
- **ORM relationships**: `hasMany`, `hasOne`, `belongsTo` with eager loading (`include`)
|
package/README.md
CHANGED
|
@@ -86,7 +86,7 @@ Every feature is built from scratch -- no npm install, no node_modules bloat, no
|
|
|
86
86
|
| **Database** | SQLite, PostgreSQL, MySQL, MSSQL/SQL Server, Firebird -- unified adapter interface, query caching (TINA4_DB_CACHE=true for 4x speedup) |
|
|
87
87
|
| **Auth** | Zero-dep JWT (HS256 + RS256), sessions (file backend), PBKDF2 password hashing, form tokens |
|
|
88
88
|
| **API** | Swagger/OpenAPI auto-generation, GraphQL with schema builder and GraphiQL IDE |
|
|
89
|
-
| **Background** | Queue (SQLite/RabbitMQ/Kafka) with priority, delayed jobs, retry, batch processing |
|
|
89
|
+
| **Background** | Queue (SQLite/RabbitMQ/Kafka/MongoDB) with priority, delayed jobs, retry, batch processing |
|
|
90
90
|
| **Real-time** | Native WebSocket (RFC 6455), per-path routing, connection manager, broadcast |
|
|
91
91
|
| **Frontend** | tina4-css (~24 KB), frond.js helper, SCSS compiler, live reload, CSS hot-reload |
|
|
92
92
|
| **DX** | Dev admin dashboard, error overlay, request inspector, hot-reload, Carbonah green benchmarks |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tina4-nodejs",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.5.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "This is not a framework. Tina4 for Node.js/TypeScript — zero deps, 38 built-in features.",
|
|
6
6
|
"keywords": ["tina4", "framework", "web", "api", "orm", "graphql", "websocket", "typescript"],
|
package/packages/cli/src/bin.ts
CHANGED
|
@@ -2,6 +2,8 @@ import { initProject } from "./commands/init.js";
|
|
|
2
2
|
import { serveProject } from "./commands/serve.js";
|
|
3
3
|
import { runMigrations } from "./commands/migrate.js";
|
|
4
4
|
import { createMigration } from "./commands/migrateCreate.js";
|
|
5
|
+
import { migrateStatus } from "./commands/migrateStatus.js";
|
|
6
|
+
import { migrateRollback } from "./commands/migrateRollback.js";
|
|
5
7
|
import { listRoutes } from "./commands/routes.js";
|
|
6
8
|
import { runTests } from "./commands/test.js";
|
|
7
9
|
import { generate } from "./commands/generate.js";
|
|
@@ -16,7 +18,9 @@ const HELP = `
|
|
|
16
18
|
tina4nodejs init [dir] Create a new Tina4 project (default: current directory)
|
|
17
19
|
tina4nodejs serve Start the dev server with hot-reload
|
|
18
20
|
tina4nodejs migrate Run pending SQL migrations
|
|
19
|
-
tina4nodejs migrate:create <desc> Create a new migration file
|
|
21
|
+
tina4nodejs migrate:create <desc> Create a new migration file pair (.sql + .down.sql)
|
|
22
|
+
tina4nodejs migrate:status Show completed and pending migrations
|
|
23
|
+
tina4nodejs migrate:rollback Roll back the last batch of migrations
|
|
20
24
|
tina4nodejs routes List all registered routes
|
|
21
25
|
tina4nodejs test [file] Run project tests
|
|
22
26
|
tina4nodejs generate <what> <name> Generate scaffolding (model, route, migration, middleware)
|
|
@@ -52,6 +56,14 @@ async function main(): Promise<void> {
|
|
|
52
56
|
await createMigration(description || undefined);
|
|
53
57
|
break;
|
|
54
58
|
}
|
|
59
|
+
case "migrate:status": {
|
|
60
|
+
await migrateStatus(args[1]);
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
case "migrate:rollback": {
|
|
64
|
+
await migrateRollback(args[1]);
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
55
67
|
case "routes": {
|
|
56
68
|
await listRoutes();
|
|
57
69
|
break;
|
|
@@ -1,8 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* CLI command: migrate — Run pending SQL migration files.
|
|
3
3
|
*
|
|
4
|
-
* Scans the migrations/ directory for .sql files
|
|
5
|
-
* and records each as applied
|
|
4
|
+
* Scans the migrations/ directory for .sql files (excluding .down.sql),
|
|
5
|
+
* executes them in order, and records each as applied with a batch number.
|
|
6
|
+
*
|
|
7
|
+
* Supports both naming patterns:
|
|
8
|
+
* - Sequential: 000001_name.sql
|
|
9
|
+
* - Timestamp: YYYYMMDDHHMMSS_name.sql
|
|
6
10
|
*/
|
|
7
11
|
import { existsSync, readdirSync, readFileSync } from "node:fs";
|
|
8
12
|
import { join, resolve } from "node:path";
|
|
@@ -45,10 +49,20 @@ export async function runMigrations(migrationDir?: string): Promise<void> {
|
|
|
45
49
|
|
|
46
50
|
ensureMigrationTable();
|
|
47
51
|
|
|
48
|
-
// Collect .sql files sorted
|
|
52
|
+
// Collect .sql files, excluding .down.sql, sorted by numeric prefix
|
|
49
53
|
const files = readdirSync(dir)
|
|
50
|
-
.filter((f) => f.endsWith(".sql"))
|
|
51
|
-
.sort()
|
|
54
|
+
.filter((f) => f.endsWith(".sql") && !f.endsWith(".down.sql"))
|
|
55
|
+
.sort((a, b) => {
|
|
56
|
+
const aMatch = a.match(/^(\d+)/);
|
|
57
|
+
const bMatch = b.match(/^(\d+)/);
|
|
58
|
+
if (aMatch && bMatch) {
|
|
59
|
+
const aNum = BigInt(aMatch[1]);
|
|
60
|
+
const bNum = BigInt(bMatch[1]);
|
|
61
|
+
if (aNum < bNum) return -1;
|
|
62
|
+
if (aNum > bNum) return 1;
|
|
63
|
+
}
|
|
64
|
+
return a.localeCompare(b);
|
|
65
|
+
});
|
|
52
66
|
|
|
53
67
|
if (files.length === 0) {
|
|
54
68
|
console.log(" No .sql migration files found.");
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* CLI command: migrate:create — Create a new SQL migration file.
|
|
2
|
+
* CLI command: migrate:create — Create a new SQL migration file pair.
|
|
3
3
|
*
|
|
4
4
|
* Usage:
|
|
5
5
|
* tina4 migrate:create "create users table"
|
|
6
6
|
* tina4 migrate:create add_email_to_users
|
|
7
7
|
*
|
|
8
|
-
* Creates
|
|
8
|
+
* Creates both:
|
|
9
|
+
* migrations/YYYYMMDDHHMMSS_description.sql (up migration)
|
|
10
|
+
* migrations/YYYYMMDDHHMMSS_description.down.sql (rollback)
|
|
9
11
|
*/
|
|
10
12
|
import { existsSync, mkdirSync, readdirSync, writeFileSync } from "node:fs";
|
|
11
13
|
import { join, resolve } from "node:path";
|
|
@@ -24,36 +26,35 @@ export async function createMigration(description?: string): Promise<void> {
|
|
|
24
26
|
mkdirSync(dir, { recursive: true });
|
|
25
27
|
}
|
|
26
28
|
|
|
27
|
-
// Determine the next sequence number
|
|
28
|
-
const existing = existsSync(dir)
|
|
29
|
-
? readdirSync(dir).filter((f) => f.endsWith(".sql")).sort()
|
|
30
|
-
: [];
|
|
31
|
-
|
|
32
|
-
let nextSeq = 1;
|
|
33
|
-
if (existing.length > 0) {
|
|
34
|
-
const last = existing[existing.length - 1];
|
|
35
|
-
const match = last.match(/^(\d+)/);
|
|
36
|
-
if (match) {
|
|
37
|
-
nextSeq = parseInt(match[1], 10) + 1;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
29
|
// Sanitise description for filename
|
|
42
30
|
const safeName = description
|
|
43
31
|
.toLowerCase()
|
|
44
32
|
.replace(/[^a-z0-9]+/g, "_")
|
|
45
33
|
.replace(/^_|_$/g, "");
|
|
46
34
|
|
|
47
|
-
|
|
48
|
-
const
|
|
49
|
-
const
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
35
|
+
// Use YYYYMMDDHHMMSS timestamp prefix
|
|
36
|
+
const now = new Date();
|
|
37
|
+
const timestamp = [
|
|
38
|
+
now.getFullYear(),
|
|
39
|
+
String(now.getMonth() + 1).padStart(2, "0"),
|
|
40
|
+
String(now.getDate()).padStart(2, "0"),
|
|
41
|
+
String(now.getHours()).padStart(2, "0"),
|
|
42
|
+
String(now.getMinutes()).padStart(2, "0"),
|
|
43
|
+
String(now.getSeconds()).padStart(2, "0"),
|
|
44
|
+
].join("");
|
|
45
|
+
|
|
46
|
+
const upFileName = `${timestamp}_${safeName}.sql`;
|
|
47
|
+
const downFileName = `${timestamp}_${safeName}.down.sql`;
|
|
48
|
+
const upPath = join(dir, upFileName);
|
|
49
|
+
const downPath = join(dir, downFileName);
|
|
50
|
+
|
|
51
|
+
const upTemplate = `-- Migration: ${description}\n-- Created: ${now.toISOString()}\n\n`;
|
|
52
|
+
const downTemplate = `-- Rollback: ${description}\n-- Created: ${now.toISOString()}\n\n`;
|
|
53
|
+
|
|
54
|
+
writeFileSync(upPath, upTemplate, "utf-8");
|
|
55
|
+
writeFileSync(downPath, downTemplate, "utf-8");
|
|
56
|
+
|
|
57
|
+
console.log(` Created migration: ${upFileName}`);
|
|
58
|
+
console.log(` Created rollback: ${downFileName}`);
|
|
59
|
+
console.log(` Path: ${dir}`);
|
|
59
60
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI command: migrate:rollback — Roll back the last batch of migrations.
|
|
3
|
+
*
|
|
4
|
+
* Looks for .down.sql files corresponding to each migration in the last batch,
|
|
5
|
+
* executes the SQL, and removes the tracking records.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* tina4 migrate:rollback
|
|
9
|
+
* tina4 migrate:rollback ./path/to/migrations
|
|
10
|
+
*/
|
|
11
|
+
import { resolve } from "node:path";
|
|
12
|
+
|
|
13
|
+
export async function migrateRollback(migrationDir?: string): Promise<void> {
|
|
14
|
+
const dir = resolve(migrationDir ?? "migrations");
|
|
15
|
+
|
|
16
|
+
let initDatabase: typeof import("@tina4/orm").initDatabase;
|
|
17
|
+
let ensureMigrationTable: typeof import("@tina4/orm").ensureMigrationTable;
|
|
18
|
+
let rollbackFn: typeof import("@tina4/orm").rollback;
|
|
19
|
+
let getLastBatchMigrations: typeof import("@tina4/orm").getLastBatchMigrations;
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
const orm = await import("@tina4/orm");
|
|
23
|
+
initDatabase = orm.initDatabase;
|
|
24
|
+
ensureMigrationTable = orm.ensureMigrationTable;
|
|
25
|
+
rollbackFn = orm.rollback;
|
|
26
|
+
getLastBatchMigrations = orm.getLastBatchMigrations;
|
|
27
|
+
} catch {
|
|
28
|
+
console.error(" Error: @tina4/orm is required to rollback migrations.");
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Ensure database is initialised
|
|
33
|
+
try {
|
|
34
|
+
initDatabase();
|
|
35
|
+
} catch {
|
|
36
|
+
// Adapter may already be set — ignore
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
ensureMigrationTable();
|
|
40
|
+
|
|
41
|
+
const lastBatch = getLastBatchMigrations();
|
|
42
|
+
if (lastBatch.length === 0) {
|
|
43
|
+
console.log(" Nothing to rollback — no migrations have been applied.");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
console.log(` Rolling back batch ${lastBatch[0].batch} (${lastBatch.length} migration(s))...`);
|
|
48
|
+
|
|
49
|
+
const rolledBack = rollbackFn(dir);
|
|
50
|
+
|
|
51
|
+
if (rolledBack.length === 0) {
|
|
52
|
+
console.log(" Nothing was rolled back.");
|
|
53
|
+
} else {
|
|
54
|
+
for (const name of rolledBack) {
|
|
55
|
+
console.log(` Rolled back: ${name}`);
|
|
56
|
+
}
|
|
57
|
+
console.log(` Rolled back ${rolledBack.length} migration(s).`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CLI command: migrate:status — Show which migrations are applied and which are pending.
|
|
3
|
+
*
|
|
4
|
+
* Usage:
|
|
5
|
+
* tina4 migrate:status
|
|
6
|
+
* tina4 migrate:status ./path/to/migrations
|
|
7
|
+
*/
|
|
8
|
+
import { resolve } from "node:path";
|
|
9
|
+
|
|
10
|
+
export async function migrateStatus(migrationDir?: string): Promise<void> {
|
|
11
|
+
const dir = resolve(migrationDir ?? "migrations");
|
|
12
|
+
|
|
13
|
+
let initDatabase: typeof import("@tina4/orm").initDatabase;
|
|
14
|
+
let ensureMigrationTable: typeof import("@tina4/orm").ensureMigrationTable;
|
|
15
|
+
let statusFn: typeof import("@tina4/orm").status;
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const orm = await import("@tina4/orm");
|
|
19
|
+
initDatabase = orm.initDatabase;
|
|
20
|
+
ensureMigrationTable = orm.ensureMigrationTable;
|
|
21
|
+
statusFn = orm.status;
|
|
22
|
+
} catch {
|
|
23
|
+
console.error(" Error: @tina4/orm is required to check migration status.");
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Ensure database is initialised
|
|
28
|
+
try {
|
|
29
|
+
initDatabase();
|
|
30
|
+
} catch {
|
|
31
|
+
// Adapter may already be set — ignore
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
ensureMigrationTable();
|
|
35
|
+
|
|
36
|
+
const result = await statusFn(undefined, { migrationsDir: dir });
|
|
37
|
+
|
|
38
|
+
console.log("");
|
|
39
|
+
console.log(" Migration Status");
|
|
40
|
+
console.log(" ─────────────────────────────────────");
|
|
41
|
+
|
|
42
|
+
if (result.completed.length === 0 && result.pending.length === 0) {
|
|
43
|
+
console.log(" No migration files found.");
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (result.completed.length > 0) {
|
|
48
|
+
console.log(` Completed (${result.completed.length}):`);
|
|
49
|
+
for (const file of result.completed) {
|
|
50
|
+
console.log(` ✓ ${file}`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (result.pending.length > 0) {
|
|
55
|
+
console.log(` Pending (${result.pending.length}):`);
|
|
56
|
+
for (const file of result.pending) {
|
|
57
|
+
console.log(` ○ ${file}`);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
console.log("");
|
|
62
|
+
}
|
|
@@ -21,7 +21,7 @@ document.getElementById('routes-count').textContent = d.count;
|
|
|
21
21
|
document.getElementById('routes-body').innerHTML = d.routes.map(r => `
|
|
22
22
|
<tr>
|
|
23
23
|
<td><span class="method method-${r.method.toLowerCase()}">${r.method}</span></td>
|
|
24
|
-
<td class="path">${r.path}</td>
|
|
24
|
+
<td class="path"><a href="${r.path}" target="_blank" title="${r.method !== 'GET' ? r.method + ' route \u2014 may not respond to browser GET' : 'Open in new tab'}" style="color:inherit;text-decoration:underline dotted;${r.method !== 'GET' ? 'opacity:0.7' : ''}">${r.path}</a></td>
|
|
25
25
|
<td>${r.auth_required ? '<span class="badge-pill bg-reserved">auth</span>' : '<span class="badge-pill bg-success">open</span>'}</td>
|
|
26
26
|
<td class="text-sm text-muted">${r.handler} <small>(${r.module})</small></td>
|
|
27
27
|
</tr>`).join('');
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";var Tina4=(()=>{var H=Object.defineProperty;var pe=Object.getOwnPropertyDescriptor;var ge=Object.getOwnPropertyNames;var he=Object.prototype.hasOwnProperty;var me=(e,n)=>{for(var t in n)H(e,t,{get:n[t],enumerable:!0})},ye=(e,n,t,o)=>{if(n&&typeof n=="object"||typeof n=="function")for(let r of ge(n))!he.call(e,r)&&r!==t&&H(e,r,{get:()=>n[r],enumerable:!(o=pe(n,r))||o.enumerable});return e};var ve=e=>ye(H({},"__esModule",{value:!0}),e);var Ae={};me(Ae,{Tina4Element:()=>I,api:()=>ie,batch:()=>q,computed:()=>z,effect:()=>m,html:()=>X,isSignal:()=>w,navigate:()=>L,pwa:()=>le,route:()=>oe,router:()=>re,signal:()=>h,ws:()=>ue});var C=null,_=null,M=null;function b(e){M=e}function J(){return M}var B=null,V=null;var N=0,P=new Set;function h(e,n){let t=e,o=new Set,r={_t4:!0,get value(){if(C&&(o.add(C),_)){let a=C;_.push(()=>o.delete(a))}return t},set value(a){if(Object.is(a,t))return;let i=t;if(t=a,r._debugInfo&&r._debugInfo.updateCount++,V&&V(r,i,a),N>0)for(let s of o)P.add(s);else{let s;for(let c of[...o])try{c()}catch(l){s===void 0&&(s=l)}if(s!==void 0)throw s}},_subscribe(a){return o.add(a),()=>{o.delete(a)}},peek(){return t}};return B&&(r._debugInfo={label:n,createdAt:Date.now(),updateCount:0,subs:o},B(r,n)),r}function z(e){let n=h(void 0);return m(()=>{n.value=e()}),{_t4:!0,get value(){return n.value},set value(t){throw new Error("[tina4] computed signals are read-only")},_subscribe(t){return n._subscribe(t)},peek(){return n.peek()}}}function m(e){let n=!1,t=[],o=()=>{if(n)return;for(let s of t)s();t=[];let a=C,i=_;C=o,_=t;try{e()}finally{C=a,_=i}};o();let r=()=>{n=!0;for(let a of t)a();t=[]};return M&&M.push(r),r}function q(e){N++;try{e()}finally{if(N--,N===0){let n=[...P];P.clear();let t;for(let o of n)try{o()}catch(r){t===void 0&&(t=r)}if(t!==void 0)throw t}}}function w(e){return e!==null&&typeof e=="object"&&e._t4===!0}var Q=new WeakMap,D="t4:";function X(e,...n){let t=Q.get(e);if(!t){t=document.createElement("template");let i="";for(let s=0;s<e.length;s++)i+=e[s],s<n.length&&(Te(i)?i+=`__t4_${s}__`:i+=`<!--${D}${s}-->`);t.innerHTML=i,Q.set(e,t)}let o=t.content.cloneNode(!0),r=be(o);for(let{marker:i,index:s}of r)ke(i,n[s]);let a=we(o);for(let i of a)Ce(i,n);return o}function be(e){let n=[];return W(e,t=>{if(t.nodeType===8){let o=t.data;if(o&&o.startsWith(D)){let r=parseInt(o.slice(D.length),10);n.push({marker:t,index:r})}}}),n}function we(e){let n=[];return W(e,t=>{t.nodeType===1&&n.push(t)}),n}function W(e,n){let t=e.childNodes;for(let o=0;o<t.length;o++){let r=t[o];n(r),W(r,n)}}function ke(e,n){let t=e.parentNode;if(t)if(w(n)){let o=document.createTextNode("");t.replaceChild(o,e),m(()=>{o.data=String(n.value??"")})}else if(typeof n=="function"){let o=document.createComment("");t.replaceChild(o,e);let r=[],a=[];m(()=>{for(let d of a)d();a=[];let i=[],s=J();b(i);let c=n();b(s),a=i;for(let d of r)d.parentNode?.removeChild(d);r=[];let l=F(c),f=o.parentNode;if(f)for(let d of l)f.insertBefore(d,o),r.push(d)})}else if(Y(n))t.replaceChild(n,e);else if(n instanceof Node)t.replaceChild(n,e);else if(Array.isArray(n)){let o=document.createDocumentFragment();for(let r of n){let a=F(r);for(let i of a)o.appendChild(i)}t.replaceChild(o,e)}else{let o=document.createTextNode(String(n??""));t.replaceChild(o,e)}}function Ce(e,n){let t=[];for(let o of Array.from(e.attributes)){let r=o.name,a=o.value;if(r.startsWith("@")){let s=r.slice(1),c=a.match(/__t4_(\d+)__/);if(c){let l=n[parseInt(c[1],10)];typeof l=="function"&&e.addEventListener(s,f=>q(()=>l(f)))}t.push(r);continue}if(r.startsWith("?")){let s=r.slice(1),c=a.match(/__t4_(\d+)__/);if(c){let l=n[parseInt(c[1],10)];if(w(l)){let f=l;m(()=>{f.value?e.setAttribute(s,""):e.removeAttribute(s)})}else typeof l=="function"?m(()=>{l()?e.setAttribute(s,""):e.removeAttribute(s)}):l&&e.setAttribute(s,"")}t.push(r);continue}if(r.startsWith(".")){let s=r.slice(1),c=a.match(/__t4_(\d+)__/);if(c){let l=n[parseInt(c[1],10)];w(l)?m(()=>{e[s]=l.value}):e[s]=l}t.push(r);continue}let i=a.match(/__t4_(\d+)__/);if(i){let s=n[parseInt(i[1],10)];if(w(s)){let c=s;m(()=>{e.setAttribute(r,String(c.value??""))})}else typeof s=="function"?m(()=>{e.setAttribute(r,String(s()??""))}):e.setAttribute(r,String(s??""))}}for(let o of t)e.removeAttribute(o)}function F(e){if(e==null||e===!1)return[];if(Y(e))return Array.from(e.childNodes);if(e instanceof Node)return[e];if(Array.isArray(e)){let n=[];for(let t of e)n.push(...F(t));return n}return[document.createTextNode(String(e))]}function Y(e){return e!=null&&typeof e=="object"&&e.nodeType===11}function Te(e){let n=!1,t=!1,o=!1;for(let r=0;r<e.length;r++){let a=e[r];a==="<"&&!n&&!t&&(o=!0),a===">"&&!n&&!t&&(o=!1),o&&(a==='"'&&!n&&(t=!t),a==="'"&&!t&&(n=!n))}return o}var Z=null,ee=null;var I=class extends HTMLElement{constructor(){super();this._props={};this._rendered=!1;let t=this.constructor;this._root=t.shadow?this.attachShadow({mode:"open"}):this;for(let[o,r]of Object.entries(t.props))this._props[o]=h(this._coerce(this.getAttribute(o),r))}static{this.props={}}static{this.styles=""}static{this.shadow=!0}static get observedAttributes(){return Object.keys(this.props)}connectedCallback(){if(this._rendered)return;this._rendered=!0;let t=this.constructor;if(t.styles&&t.shadow&&this._root instanceof ShadowRoot){let r=document.createElement("style");r.textContent=t.styles,this._root.appendChild(r)}let o=this.render();o&&this._root.appendChild(o),this.onMount(),Z&&Z(this)}disconnectedCallback(){this.onUnmount(),ee&&ee(this)}attributeChangedCallback(t,o,r){let i=this.constructor.props[t];i&&this._props[t]&&(this._props[t].value=this._coerce(r,i))}prop(t){if(!this._props[t])throw new Error(`[tina4] Prop '${t}' not declared in static props of <${this.tagName.toLowerCase()}>`);return this._props[t]}emit(t,o){this.dispatchEvent(new CustomEvent(t,{bubbles:!0,composed:!0,...o}))}onMount(){}onUnmount(){}_coerce(t,o){return o===Boolean?t!==null:o===Number?t!==null?Number(t):0:t??""}};var U=[],T=null,S="history",_e=!1,E=[],O=[],te=0;function oe(e,n){let t=[],o;e==="*"?o=".*":o=e.replace(/\{(\w+)\}/g,(a,i)=>(t.push(i),"([^/]+)"));let r=new RegExp(`^${o}$`);typeof n=="function"?U.push({pattern:e,regex:r,paramNames:t,handler:n}):U.push({pattern:e,regex:r,paramNames:t,handler:n.handler,guard:n.guard})}function L(e,n){if(S==="hash")if(n?.replace){let t=new URL(location.href);t.hash="#"+e,history.replaceState(null,"",t.toString()),R()}else location.hash="#"+e;else n?.replace?history.replaceState(null,"",e):history.pushState(null,"",e),R()}function R(){if(!T)return;let e=performance.now(),n=++te,t=S==="hash"?location.hash.slice(1)||"/":location.pathname;for(let o of U){let r=t.match(o.regex);if(!r)continue;let a={};if(o.paramNames.forEach((c,l)=>{a[c]=decodeURIComponent(r[l+1])}),o.guard){let c=o.guard();if(c===!1)return;if(typeof c=="string"){L(c,{replace:!0});return}}for(let c of O)c();O=[],T.innerHTML="";let i=[];b(i);let s=o.handler(a);if(s instanceof Promise)s.then(c=>{if(b(null),n!==te){for(let f of i)f();return}ne(T,c),O=i;let l=performance.now()-e;for(let f of E)f({path:t,params:a,pattern:o.pattern,durationMs:l})});else{b(null),ne(T,s),O=i;let c=performance.now()-e;for(let l of E)l({path:t,params:a,pattern:o.pattern,durationMs:c})}return}}function ne(e,n){n instanceof DocumentFragment||n instanceof Node?e.replaceChildren(n):typeof n=="string"?e.innerHTML=n:n!=null&&e.replaceChildren(document.createTextNode(String(n)))}var re={start(e){if(T=document.querySelector(e.target),!T)throw new Error(`[tina4] Router target '${e.target}' not found in DOM`);S=e.mode??"history",_e=!0,window.addEventListener("popstate",R),S==="hash"&&window.addEventListener("hashchange",R),document.addEventListener("click",n=>{if(n.metaKey||n.ctrlKey||n.shiftKey||n.altKey)return;let t=n.target.closest("a[href]");if(!t||t.origin!==location.origin||t.hasAttribute("target")||t.hasAttribute("download")||t.getAttribute("rel")?.includes("external"))return;n.preventDefault();let o=S==="hash"?t.getAttribute("href"):t.pathname;L(o)}),R()},on(e,n){return E.push(n),()=>{let t=E.indexOf(n);t>=0&&E.splice(t,1)}}};var y={baseUrl:"",auth:!1,tokenKey:"tina4_token",headers:{}},j=[],K=[],Se=0;function se(){try{return localStorage.getItem(y.tokenKey)}catch{return null}}function Ee(e){try{localStorage.setItem(y.tokenKey,e)}catch{}}async function x(e,n,t,o){let r={method:e,headers:{"Content-Type":"application/json",...y.headers}};if(y.auth){let d=se();d&&(r.headers.Authorization=`Bearer ${d}`)}if(t!==void 0&&e!=="GET"){let d=typeof t=="object"&&t!==null?{...t}:t;if(y.auth&&typeof d=="object"&&d!==null){let p=se();p&&(d.formToken=p)}r.body=JSON.stringify(d)}if(o?.headers&&Object.assign(r.headers,o.headers),o?.params){let d=Object.entries(o.params).map(([p,v])=>`${encodeURIComponent(p)}=${encodeURIComponent(String(v))}`).join("&");n+=(n.includes("?")?"&":"?")+d}let a=y.baseUrl+n;r._url=a,r._requestId=++Se;for(let d of j){let p=d(r);p&&(r=p)}let i=await fetch(a,r),s=i.headers.get("FreshToken");s&&Ee(s);let c=i.headers.get("Content-Type")??"",l;c.includes("json")?l=await i.json():l=await i.text();let f={status:i.status,data:l,ok:i.ok,headers:i.headers,_requestId:r._requestId};for(let d of K){let p=d(f);p&&(f=p)}if(!i.ok)throw f;return f.data}var ie={configure(e){Object.assign(y,e)},get(e,n){return x("GET",e,void 0,n)},post(e,n,t){return x("POST",e,n,t)},put(e,n,t){return x("PUT",e,n,t)},patch(e,n,t){return x("PATCH",e,n,t)},delete(e,n){return x("DELETE",e,void 0,n)},intercept(e,n){e==="request"?j.push(n):K.push(n)},_reset(){y.baseUrl="",y.auth=!1,y.tokenKey="tina4_token",y.headers={},j.length=0,K.length=0}};function ae(e){let n=e.cacheStrategy??"network-first",t=JSON.stringify(e.precache??[]),o=e.offlineRoute?`'${e.offlineRoute}'`:"null";return`
|
|
2
|
+
const CACHE = 'tina4-v1';
|
|
3
|
+
const PRECACHE = ${t};
|
|
4
|
+
const OFFLINE = ${o};
|
|
5
|
+
|
|
6
|
+
self.addEventListener('install', (e) => {
|
|
7
|
+
e.waitUntil(
|
|
8
|
+
caches.open(CACHE).then((c) => c.addAll(PRECACHE)).then(() => self.skipWaiting())
|
|
9
|
+
);
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
self.addEventListener('activate', (e) => {
|
|
13
|
+
e.waitUntil(self.clients.claim());
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
self.addEventListener('fetch', (e) => {
|
|
17
|
+
const req = e.request;
|
|
18
|
+
if (req.method !== 'GET') return;
|
|
19
|
+
|
|
20
|
+
${n==="cache-first"?`
|
|
21
|
+
e.respondWith(
|
|
22
|
+
caches.match(req).then((cached) => cached || fetch(req).then((res) => {
|
|
23
|
+
const clone = res.clone();
|
|
24
|
+
caches.open(CACHE).then((c) => c.put(req, clone));
|
|
25
|
+
return res;
|
|
26
|
+
})).catch(() => OFFLINE ? caches.match(OFFLINE) : new Response('Offline', { status: 503 }))
|
|
27
|
+
);`:n==="stale-while-revalidate"?`
|
|
28
|
+
e.respondWith(
|
|
29
|
+
caches.match(req).then((cached) => {
|
|
30
|
+
const fetched = fetch(req).then((res) => {
|
|
31
|
+
caches.open(CACHE).then((c) => c.put(req, res.clone()));
|
|
32
|
+
return res;
|
|
33
|
+
});
|
|
34
|
+
return cached || fetched;
|
|
35
|
+
}).catch(() => OFFLINE ? caches.match(OFFLINE) : new Response('Offline', { status: 503 }))
|
|
36
|
+
);`:`
|
|
37
|
+
e.respondWith(
|
|
38
|
+
fetch(req).then((res) => {
|
|
39
|
+
const clone = res.clone();
|
|
40
|
+
caches.open(CACHE).then((c) => c.put(req, clone));
|
|
41
|
+
return res;
|
|
42
|
+
}).catch(() => caches.match(req).then((cached) =>
|
|
43
|
+
cached || (OFFLINE ? caches.match(OFFLINE) : new Response('Offline', { status: 503 }))
|
|
44
|
+
))
|
|
45
|
+
);`}
|
|
46
|
+
});
|
|
47
|
+
`.trim()}function ce(e){let n={name:e.name,short_name:e.shortName??e.name,start_url:"/",display:e.display??"standalone",background_color:e.backgroundColor??"#ffffff",theme_color:e.themeColor??"#000000"};return e.icon&&(n.icons=[{src:e.icon,sizes:"192x192",type:"image/png"},{src:e.icon,sizes:"512x512",type:"image/png"}]),n}var le={register(e){let n=ce(e),t=new Blob([JSON.stringify(n)],{type:"application/json"}),o=document.createElement("link");o.rel="manifest",o.href=URL.createObjectURL(t),document.head.appendChild(o);let r=document.querySelector('meta[name="theme-color"]');if(r||(r=document.createElement("meta"),r.name="theme-color",document.head.appendChild(r)),r.content=e.themeColor??"#000000","serviceWorker"in navigator){let a=ae(e),i=new Blob([a],{type:"text/javascript"}),s=URL.createObjectURL(i);navigator.serviceWorker.register(s).catch(c=>{console.warn("[tina4] Service worker registration failed:",c)})}},generateServiceWorker(e){return ae(e)},generateManifest(e){return ce(e)}};var Re={reconnect:!0,reconnectDelay:1e3,reconnectMaxDelay:3e4,reconnectAttempts:1/0,protocols:[]};function xe(e,n={}){let t={...Re,...n},o=h("connecting"),r=h(!1),a=h(null),i=h(null),s=h(0),c={message:[],open:[],close:[],error:[]},l=null,f=!1,d=t.reconnectDelay,p=null,v=0;function de(u){if(typeof u!="string")return u;try{return JSON.parse(u)}catch{return u}}function $(){o.value=v>0?"reconnecting":"connecting";try{l=new WebSocket(e,t.protocols)}catch{o.value="closed",r.value=!1;return}l.onopen=()=>{o.value="open",r.value=!0,i.value=null,v=0,d=t.reconnectDelay,s.value=0;for(let u of c.open)u()},l.onmessage=u=>{let g=de(u.data);a.value=g;for(let k of c.message)k(g)},l.onclose=u=>{o.value="closed",r.value=!1;for(let g of c.close)g(u.code,u.reason);!f&&t.reconnect&&v<t.reconnectAttempts&&fe()},l.onerror=u=>{i.value=u;for(let g of c.error)g(u)}}function fe(){v++,s.value=v,o.value="reconnecting",p=setTimeout(()=>{p=null,$()},d),d=Math.min(d*2,t.reconnectMaxDelay)}let G={status:o,connected:r,lastMessage:a,error:i,reconnectCount:s,send(u){if(!l||l.readyState!==WebSocket.OPEN)throw new Error("[tina4] WebSocket is not connected");let g=typeof u=="string"?u:JSON.stringify(u);l.send(g)},on(u,g){return c[u].push(g),()=>{let k=c[u],A=k.indexOf(g);A>=0&&k.splice(A,1)}},pipe(u,g){let k=A=>{u.value=g(A,u.value)};return G.on("message",k)},close(u,g){f=!0,p&&(clearTimeout(p),p=null),l&&l.close(u??1e3,g??""),o.value="closed",r.value=!1}};return $(),G}var ue={connect:xe};return ve(Ae);})();
|
|
@@ -3,10 +3,10 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Uses only Node.js built-in `crypto` module. No external dependencies.
|
|
5
5
|
*
|
|
6
|
-
* import {
|
|
6
|
+
* import { getToken, validToken, hashPassword, checkPassword } from "./auth.js";
|
|
7
7
|
*
|
|
8
|
-
* const token =
|
|
9
|
-
* const payload =
|
|
8
|
+
* const token = getToken({ userId: 1 }, "my-secret");
|
|
9
|
+
* const payload = validToken(token, "my-secret");
|
|
10
10
|
*
|
|
11
11
|
* const hash = hashPassword("secret123");
|
|
12
12
|
* checkPassword("secret123", hash); // true
|
|
@@ -38,7 +38,7 @@ function base64urlDecode(str: string): Buffer {
|
|
|
38
38
|
* @param algorithm - "HS256" or "RS256" (default "HS256")
|
|
39
39
|
* @returns Signed JWT string: header.payload.signature
|
|
40
40
|
*/
|
|
41
|
-
export function
|
|
41
|
+
export function getToken(
|
|
42
42
|
payload: Record<string, unknown>,
|
|
43
43
|
secret: string,
|
|
44
44
|
expiresIn: number = 3600,
|
|
@@ -63,7 +63,7 @@ export function createToken(
|
|
|
63
63
|
/**
|
|
64
64
|
* Validate a JWT token and return the decoded payload, or null if invalid/expired.
|
|
65
65
|
*/
|
|
66
|
-
export function
|
|
66
|
+
export function validToken(
|
|
67
67
|
token: string,
|
|
68
68
|
secret: string,
|
|
69
69
|
algorithm: string = "HS256",
|
|
@@ -199,7 +199,7 @@ export function authMiddleware(secret: string, algorithm: string = "HS256"): Mid
|
|
|
199
199
|
}
|
|
200
200
|
|
|
201
201
|
const token = authHeader.slice(7);
|
|
202
|
-
const payload =
|
|
202
|
+
const payload = validToken(token, secret, algorithm);
|
|
203
203
|
|
|
204
204
|
if (payload === null) {
|
|
205
205
|
res({ error: "Unauthorized" }, 401);
|
|
@@ -229,12 +229,12 @@ export function refreshToken(
|
|
|
229
229
|
expiresIn: number = 3600,
|
|
230
230
|
algorithm: string = "HS256",
|
|
231
231
|
): string | null {
|
|
232
|
-
const payload =
|
|
232
|
+
const payload = validToken(token, secret, algorithm);
|
|
233
233
|
if (payload === null) return null;
|
|
234
234
|
|
|
235
|
-
// Strip standard timing claims so
|
|
235
|
+
// Strip standard timing claims so getToken sets fresh ones
|
|
236
236
|
const { iat: _iat, exp: _exp, ...claims } = payload;
|
|
237
|
-
return
|
|
237
|
+
return getToken(claims, secret, expiresIn, algorithm);
|
|
238
238
|
}
|
|
239
239
|
|
|
240
240
|
// ── Request Authentication ───────────────────────────────────────
|
|
@@ -258,9 +258,17 @@ export function authenticateRequest(
|
|
|
258
258
|
if (!authHeader.startsWith("Bearer ")) return null;
|
|
259
259
|
|
|
260
260
|
const token = authHeader.slice(7);
|
|
261
|
-
return
|
|
261
|
+
return validToken(token, secret, algorithm);
|
|
262
262
|
}
|
|
263
263
|
|
|
264
|
+
// ── Backward-Compatible Aliases ──────────────────────────────────
|
|
265
|
+
|
|
266
|
+
/** Alias for getToken() — kept for backward compatibility. */
|
|
267
|
+
export const createToken = getToken;
|
|
268
|
+
|
|
269
|
+
/** Alias for validToken() — kept for backward compatibility. */
|
|
270
|
+
export const validateToken = validToken;
|
|
271
|
+
|
|
264
272
|
// ── API Key Validation ───────────────────────────────────────────
|
|
265
273
|
|
|
266
274
|
/**
|
|
@@ -285,3 +293,29 @@ export function validateApiKey(
|
|
|
285
293
|
if (a.length !== b.length) return false;
|
|
286
294
|
return timingSafeEqual(a, b);
|
|
287
295
|
}
|
|
296
|
+
|
|
297
|
+
// ── Auth Class Wrapper ──────────────────────────────────────────
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Auth class that wraps the standalone auth functions so both patterns work:
|
|
301
|
+
*
|
|
302
|
+
* import { Auth } from "tina4-nodejs";
|
|
303
|
+
* const token = Auth.getToken(payload, secret);
|
|
304
|
+
*
|
|
305
|
+
* import { getToken } from "tina4-nodejs";
|
|
306
|
+
* const token = getToken(payload, secret);
|
|
307
|
+
*/
|
|
308
|
+
export class Auth {
|
|
309
|
+
static getToken = getToken;
|
|
310
|
+
static validToken = validToken;
|
|
311
|
+
static getPayload = getPayload;
|
|
312
|
+
static hashPassword = hashPassword;
|
|
313
|
+
static checkPassword = checkPassword;
|
|
314
|
+
static authMiddleware = authMiddleware;
|
|
315
|
+
static refreshToken = refreshToken;
|
|
316
|
+
static authenticateRequest = authenticateRequest;
|
|
317
|
+
static validateApiKey = validateApiKey;
|
|
318
|
+
// Legacy aliases
|
|
319
|
+
static createToken = getToken;
|
|
320
|
+
static validateToken = validToken;
|
|
321
|
+
}
|
|
@@ -862,31 +862,29 @@ const handleConnectionsTest: RouteHandler = async (req, res) => {
|
|
|
862
862
|
let version = "Connected";
|
|
863
863
|
let tableCount = 0;
|
|
864
864
|
try {
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
tableCount = Array.isArray(tables) ? tables.length : 0;
|
|
868
|
-
}
|
|
865
|
+
const tables = db.getTables();
|
|
866
|
+
tableCount = Array.isArray(tables) ? tables.length : 0;
|
|
869
867
|
} catch { tableCount = 0; }
|
|
870
868
|
try {
|
|
871
869
|
const urlLower = url.toLowerCase();
|
|
872
870
|
if (urlLower.includes("sqlite")) {
|
|
873
|
-
const row =
|
|
874
|
-
version = `SQLite ${row?.[0]?.v ?? ""}`;
|
|
871
|
+
const row = db.execute("SELECT sqlite_version() as v") as Record<string, unknown>[] | undefined;
|
|
872
|
+
version = `SQLite ${(row as any)?.[0]?.v ?? ""}`;
|
|
875
873
|
} else if (urlLower.includes("postgres")) {
|
|
876
|
-
const row =
|
|
877
|
-
version = (row?.[0]?.v ?? "PostgreSQL").toString().split(",")[0];
|
|
874
|
+
const row = db.execute("SELECT version() as v") as Record<string, unknown>[] | undefined;
|
|
875
|
+
version = ((row as any)?.[0]?.v ?? "PostgreSQL").toString().split(",")[0];
|
|
878
876
|
} else if (urlLower.includes("mysql")) {
|
|
879
|
-
const row =
|
|
880
|
-
version = `MySQL ${row?.[0]?.v ?? ""}`;
|
|
877
|
+
const row = db.execute("SELECT version() as v") as Record<string, unknown>[] | undefined;
|
|
878
|
+
version = `MySQL ${(row as any)?.[0]?.v ?? ""}`;
|
|
881
879
|
} else if (urlLower.includes("mssql")) {
|
|
882
|
-
const row =
|
|
883
|
-
version = (row?.[0]?.v ?? "MSSQL").toString().split("\n")[0];
|
|
880
|
+
const row = db.execute("SELECT @@VERSION as v") as Record<string, unknown>[] | undefined;
|
|
881
|
+
version = ((row as any)?.[0]?.v ?? "MSSQL").toString().split("\n")[0];
|
|
884
882
|
} else if (urlLower.includes("firebird")) {
|
|
885
|
-
const row =
|
|
886
|
-
version = `Firebird ${row?.[0]?.v ?? ""}`;
|
|
883
|
+
const row = db.execute("SELECT rdb$get_context('SYSTEM', 'ENGINE_VERSION') as v FROM rdb$database") as Record<string, unknown>[] | undefined;
|
|
884
|
+
version = `Firebird ${(row as any)?.[0]?.v ?? ""}`;
|
|
887
885
|
}
|
|
888
886
|
} catch { /* keep version as Connected */ }
|
|
889
|
-
|
|
887
|
+
db.close();
|
|
890
888
|
res.json({ success: true, version, tables: tableCount });
|
|
891
889
|
} catch (e: unknown) {
|
|
892
890
|
const msg = e instanceof Error ? e.message : String(e);
|
|
@@ -1085,7 +1083,7 @@ function renderDevAdminJs(): string {
|
|
|
1085
1083
|
" document.getElementById('routes-body').innerHTML = d.routes.map(function(r) {",
|
|
1086
1084
|
" return '<tr>' +",
|
|
1087
1085
|
" '<td><span class=\"method method-' + r.method.toLowerCase() + '\">' + r.method + '</span></td>' +",
|
|
1088
|
-
" '<td class=\"path\">' + (r.path || r.pattern || '') + '</td>' +",
|
|
1086
|
+
" '<td class=\"path\"><a href=\"' + (r.path || r.pattern || '') + '\" target=\"_blank\" title=\"' + (r.method !== 'GET' ? r.method + ' route \\u2014 may not respond to browser GET' : 'Open in new tab') + '\" style=\"color:inherit;text-decoration:underline dotted;' + (r.method !== 'GET' ? 'opacity:0.7' : '') + '\">' + (r.path || r.pattern || '') + '</a></td>' +",
|
|
1089
1087
|
" '<td>' + (r.auth_required || r.secure ? '<span class=\"badge-pill bg-reserved\">auth</span>' : '<span class=\"badge-pill bg-success\">open</span>') + '</td>' +",
|
|
1090
1088
|
" '<td class=\"text-sm text-muted\">' + (r.handler || '') + (r.module ? ' <small>(' + r.module + ')</small>' : '') + '</td>' +",
|
|
1091
1089
|
" '</tr>';",
|
|
@@ -13,11 +13,11 @@ export type {
|
|
|
13
13
|
} from "./types.js";
|
|
14
14
|
|
|
15
15
|
export { startServer, resolvePortAndHost } from "./server.js";
|
|
16
|
-
export { Router, RouteGroup, defaultRouter, runRouteMiddlewares } from "./router.js";
|
|
16
|
+
export { Router, RouteGroup, RouteRef, defaultRouter, runRouteMiddlewares } from "./router.js";
|
|
17
17
|
export { get, post, put, patch, del, any, websocket, del as delete } from "./router.js";
|
|
18
18
|
export type { RouteInfo } from "./router.js";
|
|
19
19
|
export { discoverRoutes } from "./routeDiscovery.js";
|
|
20
|
-
export { MiddlewareChain, cors, requestLogger } from "./middleware.js";
|
|
20
|
+
export { MiddlewareChain, MiddlewareRunner, cors, requestLogger, CorsMiddleware, RateLimiterMiddleware, RequestLogger } from "./middleware.js";
|
|
21
21
|
export type { CorsConfig } from "./middleware.js";
|
|
22
22
|
export { createRequest, parseBody } from "./request.js";
|
|
23
23
|
export { createResponse } from "./response.js";
|
|
@@ -38,10 +38,11 @@ export {
|
|
|
38
38
|
APPLICATION_OCTET, TEXT_HTML, TEXT_PLAIN, TEXT_CSV, TEXT_XML,
|
|
39
39
|
} from "./constants.js";
|
|
40
40
|
export {
|
|
41
|
-
createToken, validateToken, getPayload,
|
|
41
|
+
getToken, validToken, createToken, validateToken, getPayload,
|
|
42
42
|
hashPassword, checkPassword,
|
|
43
43
|
authMiddleware,
|
|
44
44
|
refreshToken, authenticateRequest, validateApiKey,
|
|
45
|
+
Auth,
|
|
45
46
|
} from "./auth.js";
|
|
46
47
|
export { Session, FileSessionHandler, RedisSessionHandler } from "./session.js";
|
|
47
48
|
export type { SessionConfig, SessionHandler } from "./session.js";
|
|
@@ -82,10 +83,16 @@ export { RabbitMQBackend } from "./queueBackends/rabbitmqBackend.js";
|
|
|
82
83
|
export type { RabbitMQConfig } from "./queueBackends/rabbitmqBackend.js";
|
|
83
84
|
export { KafkaBackend } from "./queueBackends/kafkaBackend.js";
|
|
84
85
|
export type { KafkaConfig } from "./queueBackends/kafkaBackend.js";
|
|
86
|
+
export { MongoBackend } from "./queueBackends/mongoBackend.js";
|
|
87
|
+
export type { MongoConfig as MongoQueueConfig } from "./queueBackends/mongoBackend.js";
|
|
88
|
+
export { DatabaseSessionHandler } from "./sessionHandlers/databaseHandler.js";
|
|
89
|
+
export type { DatabaseSessionConfig } from "./sessionHandlers/databaseHandler.js";
|
|
85
90
|
export { MongoSessionHandler } from "./sessionHandlers/mongoHandler.js";
|
|
86
91
|
export type { MongoSessionConfig } from "./sessionHandlers/mongoHandler.js";
|
|
87
92
|
export { ValkeySessionHandler } from "./sessionHandlers/valkeyHandler.js";
|
|
88
93
|
export type { ValkeySessionConfig } from "./sessionHandlers/valkeyHandler.js";
|
|
94
|
+
export { RedisNpmSessionHandler } from "./sessionHandlers/redisHandler.js";
|
|
95
|
+
export type { RedisNpmSessionConfig } from "./sessionHandlers/redisHandler.js";
|
|
89
96
|
export { tests, assertEqual, assertThrows, assertTrue, assertFalse, runAllTests, resetTests } from "./testing.js";
|
|
90
97
|
export { Container, container } from "./container.js";
|
|
91
98
|
export type { WebSocketConnection } from "./websocketConnection.js";
|