tina4-nodejs 3.10.4 → 3.10.5

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 CHANGED
@@ -1,10 +1,10 @@
1
- # CLAUDE.md — AI Developer Guide for tina4-nodejs (v3.10.4)
1
+ # CLAUDE.md — AI Developer Guide for tina4-nodejs (v3.10.5)
2
2
 
3
3
  > This file helps AI assistants (Claude, Copilot, Cursor, etc.) understand and work on this codebase effectively.
4
4
 
5
5
  ## What This Project Is
6
6
 
7
- Tina4 for Node.js/TypeScript v3.10.4 — a convention-over-configuration structural paradigm. **Not a framework.** The developer writes TypeScript; Tina4 is invisible infrastructure.
7
+ Tina4 for Node.js/TypeScript v3.10.5 — a convention-over-configuration structural paradigm. **Not a framework.** The developer writes TypeScript; Tina4 is invisible infrastructure.
8
8
 
9
9
  The philosophy: zero ceremony, batteries included, file system as source of truth.
10
10
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tina4-nodejs",
3
- "version": "3.10.4",
3
+ "version": "3.10.5",
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"],
@@ -219,9 +219,76 @@ function resolveVar(expr: string, context: Record<string, unknown>): unknown {
219
219
  return value;
220
220
  }
221
221
 
222
+ function findOutsideQuotes(expr: string, needle: string): number {
223
+ let inQuote: string | null = null;
224
+ let depth = 0;
225
+ let i = 0;
226
+ while (i <= expr.length - needle.length) {
227
+ const ch = expr[i];
228
+ if ((ch === '"' || ch === "'") && depth === 0) {
229
+ if (inQuote === null) {
230
+ inQuote = ch;
231
+ } else if (ch === inQuote) {
232
+ inQuote = null;
233
+ }
234
+ i++;
235
+ continue;
236
+ }
237
+ if (inQuote) { i++; continue; }
238
+ if (ch === "(") depth++;
239
+ else if (ch === ")") depth--;
240
+ if (depth === 0 && expr.slice(i, i + needle.length) === needle) {
241
+ return i;
242
+ }
243
+ i++;
244
+ }
245
+ return -1;
246
+ }
247
+
248
+ function splitOutsideQuotes(expr: string, sep: string): string[] {
249
+ const parts: string[] = [];
250
+ let currentStart = 0;
251
+ let inQuote: string | null = null;
252
+ let depth = 0;
253
+ let i = 0;
254
+ while (i <= expr.length - sep.length) {
255
+ const ch = expr[i];
256
+ if ((ch === '"' || ch === "'") && depth === 0) {
257
+ if (inQuote === null) {
258
+ inQuote = ch;
259
+ } else if (ch === inQuote) {
260
+ inQuote = null;
261
+ }
262
+ i++;
263
+ continue;
264
+ }
265
+ if (inQuote) { i++; continue; }
266
+ if (ch === "(") depth++;
267
+ else if (ch === ")") depth--;
268
+ if (depth === 0 && expr.slice(i, i + sep.length) === sep) {
269
+ parts.push(expr.slice(currentStart, i));
270
+ i += sep.length;
271
+ currentStart = i;
272
+ continue;
273
+ }
274
+ i++;
275
+ }
276
+ parts.push(expr.slice(currentStart));
277
+ return parts;
278
+ }
279
+
222
280
  function evalExpr(expr: string, context: Record<string, unknown>): unknown {
223
281
  expr = expr.trim();
224
282
 
283
+ // String literal early-return: if the entire expression is a single quoted
284
+ // string with no unescaped matching quotes inside, return its content.
285
+ if (expr.length >= 2) {
286
+ const q = expr[0];
287
+ if ((q === '"' || q === "'") && expr.endsWith(q) && !expr.slice(1, -1).includes(q)) {
288
+ return expr.slice(1, -1);
289
+ }
290
+ }
291
+
225
292
  // Ternary: condition ? true_val : false_val
226
293
  // Match carefully to handle nested ternaries
227
294
  const ternaryIdx = findTernary(expr);
@@ -245,7 +312,7 @@ function evalExpr(expr: string, context: Record<string, unknown>): unknown {
245
312
  }
246
313
 
247
314
  // Null coalescing: value ?? "default"
248
- const qqIdx = expr.indexOf("??");
315
+ const qqIdx = findOutsideQuotes(expr, "??");
249
316
  if (qqIdx !== -1) {
250
317
  const left = expr.slice(0, qqIdx).trim();
251
318
  const right = expr.slice(qqIdx + 2).trim();
@@ -257,8 +324,8 @@ function evalExpr(expr: string, context: Record<string, unknown>): unknown {
257
324
  }
258
325
 
259
326
  // String concatenation with ~
260
- if (expr.includes("~")) {
261
- const parts = splitOnTilde(expr);
327
+ if (findOutsideQuotes(expr, "~") >= 0) {
328
+ const parts = splitOutsideQuotes(expr, "~");
262
329
  if (parts.length > 1) {
263
330
  return parts.map(p => {
264
331
  const v = evalExpr(p.trim(), context);
@@ -269,7 +336,7 @@ function evalExpr(expr: string, context: Record<string, unknown>): unknown {
269
336
 
270
337
  // Check for comparison/logical operators
271
338
  for (const op of [" not in ", " in ", " is not ", " is ", "!=", "==", ">=", "<=", ">", "<", " and ", " or ", " not "]) {
272
- if (expr.includes(op)) {
339
+ if (findOutsideQuotes(expr, op) >= 0) {
273
340
  return evalComparison(expr, context);
274
341
  }
275
342
  }