tina4-nodejs 3.13.85 → 3.13.86

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.
@@ -2,7 +2,7 @@
2
2
  * Tina4 Session — Pluggable session backends, zero core dependencies.
3
3
  *
4
4
  * File-based sessions by default. Redis backend available via raw TCP (no ioredis needed).
5
- * Database (SQLite) backend available via better-sqlite3.
5
+ * Database (SQLite) backend available via Node's built-in node:sqlite.
6
6
  *
7
7
  * import { Session, RedisSessionHandler } from "@tina4/core";
8
8
  *
@@ -15,7 +15,7 @@
15
15
  * redisPort: 6379,
16
16
  * });
17
17
  *
18
- * // Database backend (SQLite via better-sqlite3)
18
+ * // Database backend (SQLite via node:sqlite)
19
19
  * const session = new Session("database");
20
20
  * // or: new Session("db");
21
21
  *
@@ -1,14 +1,13 @@
1
1
  /**
2
- * Tina4 Database Session Handler — SQLite via better-sqlite3, zero extra dependencies.
2
+ * Tina4 Database Session Handler — SQLite via Node's built-in node:sqlite,
3
+ * zero extra dependencies.
3
4
  *
4
- * Uses the same `better-sqlite3` library the ORM already depends on.
5
+ * Uses the same `node:sqlite` (DatabaseSync) the ORM's SQLite adapter uses —
6
+ * no third-party driver, nothing to install.
5
7
  * Stores sessions in a `tina4_session` table with JSON data and expiry.
6
8
  *
7
9
  * Configure via environment variables:
8
10
  * TINA4_DATABASE_URL (default: "sqlite:///data/tina4_sessions.db")
9
- *
10
- * The handler dynamically imports `better-sqlite3` and throws a clear
11
- * error if the package is not installed.
12
11
  */
13
12
  import { DatabaseSync } from "node:sqlite";
14
13
  import type { SessionHandler } from "../session.js";
@@ -35,7 +34,7 @@ export interface DatabaseSessionConfig {
35
34
  }
36
35
 
37
36
  /**
38
- * Database session handler using better-sqlite3 (synchronous SQLite).
37
+ * Database session handler using node:sqlite (synchronous SQLite).
39
38
  *
40
39
  * Stores session data as JSON in a `tina4_session` table.
41
40
  * Expiry is checked on read; expired rows are cleaned up lazily.
@@ -316,11 +316,14 @@ function resolveVar(expr, context) {
316
316
  return value;
317
317
  }
318
318
  function findOutsideQuotes(expr, needle) {
319
+ if (!expr.includes(needle)) return -1;
319
320
  let inQuote = null;
320
321
  let depth = 0;
321
322
  let bracketDepth = 0;
322
323
  let i = 0;
323
- while (i <= expr.length - needle.length) {
324
+ const needleLen = needle.length;
325
+ const lastStart = expr.length - needleLen;
326
+ while (i <= lastStart) {
324
327
  const ch = expr[i];
325
328
  if ((ch === '"' || ch === "'") && depth === 0 && bracketDepth === 0) {
326
329
  if (inQuote === null) {
@@ -339,7 +342,7 @@ function findOutsideQuotes(expr, needle) {
339
342
  else if (ch === ")") depth--;
340
343
  else if (ch === "[") bracketDepth++;
341
344
  else if (ch === "]") bracketDepth--;
342
- if (depth === 0 && bracketDepth === 0 && expr.slice(i, i + needle.length) === needle) {
345
+ if (depth === 0 && bracketDepth === 0 && expr.startsWith(needle, i)) {
343
346
  return i;
344
347
  }
345
348
  i++;
@@ -347,13 +350,16 @@ function findOutsideQuotes(expr, needle) {
347
350
  return -1;
348
351
  }
349
352
  function splitOutsideQuotes(expr, sep) {
353
+ if (!expr.includes(sep)) return [expr];
350
354
  const parts = [];
351
355
  let currentStart = 0;
352
356
  let inQuote = null;
353
357
  let depth = 0;
354
358
  let bracketDepth = 0;
355
359
  let i = 0;
356
- while (i <= expr.length - sep.length) {
360
+ const sepLen = sep.length;
361
+ const lastStart = expr.length - sepLen;
362
+ while (i <= lastStart) {
357
363
  const ch = expr[i];
358
364
  if ((ch === '"' || ch === "'") && depth === 0 && bracketDepth === 0) {
359
365
  if (inQuote === null) {
@@ -372,9 +378,9 @@ function splitOutsideQuotes(expr, sep) {
372
378
  else if (ch === ")") depth--;
373
379
  else if (ch === "[") bracketDepth++;
374
380
  else if (ch === "]") bracketDepth--;
375
- if (depth === 0 && bracketDepth === 0 && expr.slice(i, i + sep.length) === sep) {
381
+ if (depth === 0 && bracketDepth === 0 && expr.startsWith(sep, i)) {
376
382
  parts.push(expr.slice(currentStart, i));
377
- i += sep.length;
383
+ i += sepLen;
378
384
  currentStart = i;
379
385
  continue;
380
386
  }
@@ -444,11 +444,23 @@ function resolveVar(expr: string, context: Record<string, unknown>): unknown {
444
444
  }
445
445
 
446
446
  function findOutsideQuotes(expr: string, needle: string): number {
447
+ // Fast path. This is the hottest function in a render: profiling the Python
448
+ // twin showed 415,200 calls and 53% of render time for one 20-row loop
449
+ // template, and the overwhelming majority return -1 because the needle simply
450
+ // is not in the expression. includes() is a native scan, so bailing here skips
451
+ // the whole JS character loop. Exact, not a heuristic: a needle absent from
452
+ // the string cannot be present outside quotes either.
453
+ if (!expr.includes(needle)) return -1;
454
+
447
455
  let inQuote: string | null = null;
448
456
  let depth = 0;
449
457
  let bracketDepth = 0;
450
458
  let i = 0;
451
- while (i <= expr.length - needle.length) {
459
+ // Hoisted out of the loop condition -- both lengths were recomputed on every
460
+ // single iteration.
461
+ const needleLen = needle.length;
462
+ const lastStart = expr.length - needleLen;
463
+ while (i <= lastStart) {
452
464
  const ch = expr[i];
453
465
  if ((ch === '"' || ch === "'") && depth === 0 && bracketDepth === 0) {
454
466
  if (inQuote === null) {
@@ -464,7 +476,9 @@ function findOutsideQuotes(expr: string, needle: string): number {
464
476
  else if (ch === ")") depth--;
465
477
  else if (ch === "[") bracketDepth++;
466
478
  else if (ch === "]") bracketDepth--;
467
- if (depth === 0 && bracketDepth === 0 && expr.slice(i, i + needle.length) === needle) {
479
+ // startsWith(needle, i) rather than slice(i, i + needleLen) === needle: the
480
+ // slice allocated a throwaway string at every character position.
481
+ if (depth === 0 && bracketDepth === 0 && expr.startsWith(needle, i)) {
468
482
  return i;
469
483
  }
470
484
  i++;
@@ -473,13 +487,20 @@ function findOutsideQuotes(expr: string, needle: string): number {
473
487
  }
474
488
 
475
489
  function splitOutsideQuotes(expr: string, sep: string): string[] {
490
+ // Fast path, same reasoning as findOutsideQuotes: no separator anywhere means
491
+ // no split, and includes() is a native scan versus a JS character loop.
492
+ if (!expr.includes(sep)) return [expr];
493
+
476
494
  const parts: string[] = [];
477
495
  let currentStart = 0;
478
496
  let inQuote: string | null = null;
479
497
  let depth = 0;
480
498
  let bracketDepth = 0;
481
499
  let i = 0;
482
- while (i <= expr.length - sep.length) {
500
+ // Hoisted out of the loop condition -- recomputed every iteration before.
501
+ const sepLen = sep.length;
502
+ const lastStart = expr.length - sepLen;
503
+ while (i <= lastStart) {
483
504
  const ch = expr[i];
484
505
  if ((ch === '"' || ch === "'") && depth === 0 && bracketDepth === 0) {
485
506
  if (inQuote === null) {
@@ -495,9 +516,10 @@ function splitOutsideQuotes(expr: string, sep: string): string[] {
495
516
  else if (ch === ")") depth--;
496
517
  else if (ch === "[") bracketDepth++;
497
518
  else if (ch === "]") bracketDepth--;
498
- if (depth === 0 && bracketDepth === 0 && expr.slice(i, i + sep.length) === sep) {
519
+ // startsWith avoids allocating a throwaway slice at every position.
520
+ if (depth === 0 && bracketDepth === 0 && expr.startsWith(sep, i)) {
499
521
  parts.push(expr.slice(currentStart, i));
500
- i += sep.length;
522
+ i += sepLen;
501
523
  currentStart = i;
502
524
  continue;
503
525
  }