@doki-land/dejavu-engine 0.0.0 → 0.0.8

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.
@@ -0,0 +1,7 @@
1
+ /**
2
+ * HTML escape — 转义 Dejavu 输出中的 5 个危险字符。
3
+ *
4
+ * 与 Rust `dejavu-types` 的 `html_escape` 过滤器、Node stub 以及
5
+ * legacy TS engine 行为完全一致。只处理 `& < > " '`。
6
+ */
7
+ export declare function htmlEscape(str: string): string;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * HTML escape — 转义 Dejavu 输出中的 5 个危险字符。
3
+ *
4
+ * 与 Rust `dejavu-types` 的 `html_escape` 过滤器、Node stub 以及
5
+ * legacy TS engine 行为完全一致。只处理 `& < > " '`。
6
+ */
7
+ export function htmlEscape(str) {
8
+ return String(str)
9
+ .replace(/&/g, "&amp;")
10
+ .replace(/</g, "&lt;")
11
+ .replace(/>/g, "&gt;")
12
+ .replace(/"/g, "&quot;")
13
+ .replace(/'/g, "&#39;");
14
+ }
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Dejavu 1.0 支持的二元运算符。
3
+ * 与 Rust `dejavu-ir::IrExprBinaryOp` 枚举一一对应。
4
+ *
5
+ * 行为:
6
+ * - `+` 特殊:任何一侧为字符串时做字符串拼接,否则数值加法。
7
+ * - `&&` / `||` 返回原值(短路),不是布尔。
8
+ * - `in`:R 是 object / array 时检查 key 存在。
9
+ * - 比较与相等用 JS 原生语义。
10
+ */
11
+ export declare function binary(op: string, l: unknown, r: unknown): unknown;
12
+ /**
13
+ * 一元运算符。
14
+ *
15
+ * `!` 使用 **Dejavu truthiness**(不是 JS falsy)。
16
+ */
17
+ export declare function unary(op: string, a: unknown): unknown;
18
+ /**
19
+ * 成员访问 —— `obj?.[property] ?? null`。
20
+ * 对不存在的键返回 null(不是 undefined)。
21
+ */
22
+ export declare function exprMember(obj: unknown, property: string): unknown;
23
+ /**
24
+ * 索引访问 —— 数组用数字索引(|0 截断),对象用 String(index)。
25
+ * 越界 / 不存在返回 null。
26
+ */
27
+ export declare function exprIndex(obj: unknown, index: unknown): unknown;
@@ -0,0 +1,104 @@
1
+ import { isTruthy } from "./value.js";
2
+ /**
3
+ * Dejavu 1.0 支持的二元运算符。
4
+ * 与 Rust `dejavu-ir::IrExprBinaryOp` 枚举一一对应。
5
+ *
6
+ * 行为:
7
+ * - `+` 特殊:任何一侧为字符串时做字符串拼接,否则数值加法。
8
+ * - `&&` / `||` 返回原值(短路),不是布尔。
9
+ * - `in`:R 是 object / array 时检查 key 存在。
10
+ * - 比较与相等用 JS 原生语义。
11
+ */
12
+ export function binary(op, l, r) {
13
+ switch (op) {
14
+ case "+": {
15
+ if (typeof l === "string" || typeof r === "string") {
16
+ return String(l) + String(r);
17
+ }
18
+ return l + r;
19
+ }
20
+ case "-":
21
+ return l - r;
22
+ case "*":
23
+ return l * r;
24
+ case "/":
25
+ return l / r;
26
+ case "%":
27
+ return l % r;
28
+ case "==":
29
+ // Dejavu 1.0 只用 ==/!= —— JS 宽松相等
30
+ // eslint-disable-next-line eqeqeq
31
+ return l == r;
32
+ case "!=":
33
+ // eslint-disable-next-line eqeqeq
34
+ return l != r;
35
+ case "===":
36
+ return l === r;
37
+ case "!==":
38
+ return l !== r;
39
+ case "<":
40
+ return l < r;
41
+ case "<=":
42
+ return l <= r;
43
+ case ">":
44
+ return l > r;
45
+ case ">=":
46
+ return l >= r;
47
+ case "&&":
48
+ return l && r;
49
+ case "||":
50
+ return l || r;
51
+ case "in":
52
+ if (r === null || r === undefined)
53
+ return false;
54
+ return String(l) in r;
55
+ default:
56
+ return null;
57
+ }
58
+ }
59
+ /**
60
+ * 一元运算符。
61
+ *
62
+ * `!` 使用 **Dejavu truthiness**(不是 JS falsy)。
63
+ */
64
+ export function unary(op, a) {
65
+ switch (op) {
66
+ case "!":
67
+ return !isTruthy(a);
68
+ case "-":
69
+ return -(Number(a) || 0);
70
+ case "+":
71
+ return +(Number(a) || 0);
72
+ default:
73
+ return null;
74
+ }
75
+ }
76
+ /**
77
+ * 成员访问 —— `obj?.[property] ?? null`。
78
+ * 对不存在的键返回 null(不是 undefined)。
79
+ */
80
+ export function exprMember(obj, property) {
81
+ if (obj === null || obj === undefined)
82
+ return null;
83
+ const target = obj;
84
+ const v = target[property];
85
+ return v === undefined ? null : v;
86
+ }
87
+ /**
88
+ * 索引访问 —— 数组用数字索引(|0 截断),对象用 String(index)。
89
+ * 越界 / 不存在返回 null。
90
+ */
91
+ export function exprIndex(obj, index) {
92
+ if (obj === null || obj === undefined)
93
+ return null;
94
+ if (Array.isArray(obj)) {
95
+ const i = Number(index) | 0;
96
+ const v = obj[i];
97
+ return v === undefined ? null : v;
98
+ }
99
+ if (typeof obj === "object") {
100
+ const v = obj[String(index)];
101
+ return v === undefined ? null : v;
102
+ }
103
+ return null;
104
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Dejavu 1.0 合同过滤器集。
3
+ *
4
+ * 与 Rust `dejavu-types` 过滤器系统(见 `projects/crates/dejavu-types/src/values/filter.rs`)
5
+ * 以及 `dejavu-codegen` Node stub 对齐。
6
+ *
7
+ * 未识别的过滤器 **宽松返回原值**(与 Rust 渲染器一致,不抛错)。
8
+ */
9
+ export declare function applyFilter(name: string, value: unknown, args: unknown[]): unknown;
@@ -0,0 +1,87 @@
1
+ import { htmlEscape } from "./escape.js";
2
+ import { isTruthy, valueToString } from "./value.js";
3
+ /**
4
+ * Dejavu 1.0 合同过滤器集。
5
+ *
6
+ * 与 Rust `dejavu-types` 过滤器系统(见 `projects/crates/dejavu-types/src/values/filter.rs`)
7
+ * 以及 `dejavu-codegen` Node stub 对齐。
8
+ *
9
+ * 未识别的过滤器 **宽松返回原值**(与 Rust 渲染器一致,不抛错)。
10
+ */
11
+ export function applyFilter(name, value, args) {
12
+ switch (name) {
13
+ case "escape":
14
+ case "e":
15
+ return htmlEscape(valueToString(value));
16
+ case "raw":
17
+ case "safe":
18
+ case "s":
19
+ return valueToString(value);
20
+ case "uppercase":
21
+ case "upper":
22
+ return String(value).toUpperCase();
23
+ case "lowercase":
24
+ case "lower":
25
+ return String(value).toLowerCase();
26
+ case "trim":
27
+ return String(value).trim();
28
+ case "default":
29
+ return isTruthy(value) ? value : args[0] ?? "";
30
+ case "length": {
31
+ if (value === null || value === undefined)
32
+ return 0;
33
+ if (Array.isArray(value))
34
+ return value.length;
35
+ if (typeof value === "string")
36
+ return value.length;
37
+ const maybe = value.length;
38
+ return typeof maybe === "number" ? maybe : String(value).length;
39
+ }
40
+ case "join":
41
+ return Array.isArray(value)
42
+ ? value.join(String(args[0] ?? ","))
43
+ : String(value);
44
+ case "replace":
45
+ return String(value).replace(args[0] ?? "", String(args[1] ?? ""));
46
+ case "date": {
47
+ let d = null;
48
+ if (value instanceof Date) {
49
+ d = value;
50
+ }
51
+ else if (typeof value === "number") {
52
+ d = new Date(value);
53
+ }
54
+ else if (typeof value === "string") {
55
+ const parsed = new Date(value);
56
+ if (!Number.isNaN(parsed.getTime()))
57
+ d = parsed;
58
+ }
59
+ if (!d)
60
+ return "";
61
+ // 第一期只支持 ISO 8601(args[0] 不使用 —— Rust 过滤器默认也是 ISO)。
62
+ return d.toISOString();
63
+ }
64
+ case "slug":
65
+ return slugify(String(value));
66
+ default:
67
+ return value;
68
+ }
69
+ }
70
+ /**
71
+ * 与 Rust `slug_filter` 对齐:
72
+ *
73
+ * 1. NFKD Unicode 分解
74
+ * 2. 去掉 combining marks
75
+ * 3. 小写
76
+ * 4. 非字母数字 / `-` → `-`
77
+ * 5. 连续 `-` 合并
78
+ * 6. 去首尾 `-`
79
+ */
80
+ function slugify(input) {
81
+ const decomposed = input
82
+ .normalize("NFKD")
83
+ .replace(/[\u0300-\u036f]/g, "")
84
+ .toLowerCase();
85
+ const replaced = decomposed.replace(/[^a-z0-9-]+/g, "-");
86
+ return replaced.replace(/-+/g, "-").replace(/^-+|-+$/g, "");
87
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Dejavu AOT runtime helpers —— dejavu-codegen 产出的 ESM 模块会 import 本文件。
3
+ *
4
+ * 所有导出都是纯函数,无副作用、无状态。
5
+ * 与 Rust `dejavu-codegen` Node stub 以及 `dejavu-types` 的 filter 系统语义一致。
6
+ */
7
+ export { htmlEscape } from "./escape.js";
8
+ export { valueToString, isTruthy } from "./value.js";
9
+ export { applyFilter } from "./filter.js";
10
+ export { binary, unary, exprMember, exprIndex } from "./expr.js";
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Dejavu AOT runtime helpers —— dejavu-codegen 产出的 ESM 模块会 import 本文件。
3
+ *
4
+ * 所有导出都是纯函数,无副作用、无状态。
5
+ * 与 Rust `dejavu-codegen` Node stub 以及 `dejavu-types` 的 filter 系统语义一致。
6
+ */
7
+ export { htmlEscape } from "./escape.js";
8
+ export { valueToString, isTruthy } from "./value.js";
9
+ export { applyFilter } from "./filter.js";
10
+ export { binary, unary, exprMember, exprIndex } from "./expr.js";
@@ -0,0 +1,24 @@
1
+ /**
2
+ * 1.0 合同的 truthiness —— 与 JS falsy 不同:
3
+ *
4
+ * - null / undefined → false
5
+ * - "" → false
6
+ * - 0 (number) / false (boolean) → false
7
+ * - 空数组 [] → false
8
+ * - 空对象 {} → false
9
+ * - "0" (字符串) → **true**
10
+ * - 其他 → true
11
+ */
12
+ export declare function isTruthy(v: unknown): boolean;
13
+ /**
14
+ * 将任意 JS 值渲染为字符串。与 Rust 渲染器的 debug-print 语义对齐:
15
+ *
16
+ * - null / undefined → ""
17
+ * - boolean → "true" / "false"
18
+ * - number → String()(NaN / Infinity 保持原样)
19
+ * - Date → toISOString()
20
+ * - Array → 递归 valueToString 后 "," join
21
+ * - object → JSON.stringify(fallback 到 String())
22
+ * - string / 其他 → String()
23
+ */
24
+ export declare function valueToString(v: unknown): string;
@@ -0,0 +1,60 @@
1
+ /**
2
+ * 1.0 合同的 truthiness —— 与 JS falsy 不同:
3
+ *
4
+ * - null / undefined → false
5
+ * - "" → false
6
+ * - 0 (number) / false (boolean) → false
7
+ * - 空数组 [] → false
8
+ * - 空对象 {} → false
9
+ * - "0" (字符串) → **true**
10
+ * - 其他 → true
11
+ */
12
+ export function isTruthy(v) {
13
+ if (v === null || v === undefined)
14
+ return false;
15
+ if (v === "")
16
+ return false;
17
+ if (v === false)
18
+ return false;
19
+ if (v === 0)
20
+ return false;
21
+ if (Array.isArray(v) && v.length === 0)
22
+ return false;
23
+ if (typeof v === "object" && !Array.isArray(v)) {
24
+ return Object.keys(v).length > 0;
25
+ }
26
+ return true;
27
+ }
28
+ /**
29
+ * 将任意 JS 值渲染为字符串。与 Rust 渲染器的 debug-print 语义对齐:
30
+ *
31
+ * - null / undefined → ""
32
+ * - boolean → "true" / "false"
33
+ * - number → String()(NaN / Infinity 保持原样)
34
+ * - Date → toISOString()
35
+ * - Array → 递归 valueToString 后 "," join
36
+ * - object → JSON.stringify(fallback 到 String())
37
+ * - string / 其他 → String()
38
+ */
39
+ export function valueToString(v) {
40
+ if (v === null || v === undefined)
41
+ return "";
42
+ if (typeof v === "boolean")
43
+ return v ? "true" : "false";
44
+ if (typeof v === "number") {
45
+ return String(v);
46
+ }
47
+ if (v instanceof Date)
48
+ return v.toISOString();
49
+ if (Array.isArray(v))
50
+ return v.map(valueToString).join(",");
51
+ if (typeof v === "object") {
52
+ try {
53
+ return JSON.stringify(v);
54
+ }
55
+ catch {
56
+ return String(v);
57
+ }
58
+ }
59
+ return String(v);
60
+ }
package/package.json CHANGED
@@ -1,41 +1,47 @@
1
1
  {
2
2
  "name": "@doki-land/dejavu-engine",
3
- "version": "0.0.0",
4
- "description": "DejaVu Engine - 模板引擎",
3
+ "version": "0.0.8",
4
+ "description": "Dejavu pure TypeScript AOT runtime — generated template modules depend on this package",
5
5
  "type": "module",
6
+ "license": "MPL-2.0",
7
+ "author": "Doki Land",
8
+ "homepage": "https://github.com/doki-land/dejavu-language/tree/dev/projects/packages/dejavu-engine",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/doki-land/dejavu-language.git",
12
+ "directory": "projects/packages/dejavu-engine"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/doki-land/dejavu-language/issues"
16
+ },
17
+ "engines": {
18
+ "node": ">=20"
19
+ },
6
20
  "main": "./dist/index.js",
7
- "module": "./dist/index.js",
8
21
  "types": "./dist/index.d.ts",
9
- "bin": {
10
- "dejavu": "./bin/dejavu.mjs"
11
- },
12
22
  "exports": {
13
23
  ".": {
14
24
  "types": "./dist/index.d.ts",
15
- "import": "./dist/index.js"
25
+ "default": "./dist/index.js"
26
+ },
27
+ "./runtime": {
28
+ "types": "./dist/runtime/index.d.ts",
29
+ "default": "./dist/runtime/index.js"
16
30
  }
17
31
  },
18
32
  "files": [
19
- "dist",
20
- "bin"
33
+ "dist"
21
34
  ],
22
- "keywords": [],
23
- "author": "",
24
- "license": "MIT",
25
- "devDependencies": {
26
- "@types/node": "^25.4.0",
27
- "jsdom": "^29.0.0",
28
- "terser": "^5.46.1",
29
- "tsup": "^8.0.0",
30
- "typescript": "^5.0.0",
31
- "vite": "^5.0.0",
32
- "vitest": "^2.1.8"
33
- },
34
35
  "scripts": {
35
- "build": "tsup src/index.ts --format esm",
36
- "build:dts": "tsup src/index.ts --format esm --dts",
37
- "dev": "tsup src/index.ts --format esm --watch",
36
+ "build": "tsc -p tsconfig.json",
38
37
  "test": "vitest run",
39
38
  "typecheck": "tsc --noEmit"
39
+ },
40
+ "devDependencies": {
41
+ "vitest": "^2.1.8",
42
+ "typescript": "^5.8.0"
43
+ },
44
+ "publishConfig": {
45
+ "access": "public"
40
46
  }
41
- }
47
+ }