@lokascript/framework 2.0.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.
Files changed (181) hide show
  1. package/LICENSE +20 -0
  2. package/README.md +142 -0
  3. package/dist/aot/aot-orchestrator.d.ts +75 -0
  4. package/dist/aot/aot-orchestrator.d.ts.map +1 -0
  5. package/dist/aot/domain-scanner.d.ts +27 -0
  6. package/dist/aot/domain-scanner.d.ts.map +1 -0
  7. package/dist/aot/index.d.ts +8 -0
  8. package/dist/aot/index.d.ts.map +1 -0
  9. package/dist/aot/types.d.ts +103 -0
  10. package/dist/aot/types.d.ts.map +1 -0
  11. package/dist/api/create-dsl.d.ts +91 -0
  12. package/dist/api/create-dsl.d.ts.map +1 -0
  13. package/dist/api/dispatcher.d.ts +108 -0
  14. package/dist/api/dispatcher.d.ts.map +1 -0
  15. package/dist/api/domain-registry.d.ts +152 -0
  16. package/dist/api/domain-registry.d.ts.map +1 -0
  17. package/dist/api/index.d.ts +7 -0
  18. package/dist/api/index.d.ts.map +1 -0
  19. package/dist/api/index.js +2082 -0
  20. package/dist/api/index.js.map +1 -0
  21. package/dist/core/index.d.ts +7 -0
  22. package/dist/core/index.d.ts.map +1 -0
  23. package/dist/core/index.js +2674 -0
  24. package/dist/core/index.js.map +1 -0
  25. package/dist/core/logger.d.ts +32 -0
  26. package/dist/core/logger.d.ts.map +1 -0
  27. package/dist/core/pattern-matching/index.d.ts +6 -0
  28. package/dist/core/pattern-matching/index.d.ts.map +1 -0
  29. package/dist/core/pattern-matching/index.js +1239 -0
  30. package/dist/core/pattern-matching/index.js.map +1 -0
  31. package/dist/core/pattern-matching/pattern-matcher.d.ts +239 -0
  32. package/dist/core/pattern-matching/pattern-matcher.d.ts.map +1 -0
  33. package/dist/core/pattern-matching/utils/index.d.ts +6 -0
  34. package/dist/core/pattern-matching/utils/index.d.ts.map +1 -0
  35. package/dist/core/pattern-matching/utils/possessive-keywords.d.ts +38 -0
  36. package/dist/core/pattern-matching/utils/possessive-keywords.d.ts.map +1 -0
  37. package/dist/core/pattern-matching/utils/type-validation.d.ts +63 -0
  38. package/dist/core/pattern-matching/utils/type-validation.d.ts.map +1 -0
  39. package/dist/core/tokenization/base-tokenizer.d.ts +344 -0
  40. package/dist/core/tokenization/base-tokenizer.d.ts.map +1 -0
  41. package/dist/core/tokenization/char-classifiers.d.ts +56 -0
  42. package/dist/core/tokenization/char-classifiers.d.ts.map +1 -0
  43. package/dist/core/tokenization/default-extractors.d.ts +48 -0
  44. package/dist/core/tokenization/default-extractors.d.ts.map +1 -0
  45. package/dist/core/tokenization/extractors/index.d.ts +9 -0
  46. package/dist/core/tokenization/extractors/index.d.ts.map +1 -0
  47. package/dist/core/tokenization/extractors/operator.d.ts +23 -0
  48. package/dist/core/tokenization/extractors/operator.d.ts.map +1 -0
  49. package/dist/core/tokenization/extractors/punctuation.d.ts +22 -0
  50. package/dist/core/tokenization/extractors/punctuation.d.ts.map +1 -0
  51. package/dist/core/tokenization/extractors.d.ts +61 -0
  52. package/dist/core/tokenization/extractors.d.ts.map +1 -0
  53. package/dist/core/tokenization/index.d.ts +11 -0
  54. package/dist/core/tokenization/index.d.ts.map +1 -0
  55. package/dist/core/tokenization/index.js +1345 -0
  56. package/dist/core/tokenization/index.js.map +1 -0
  57. package/dist/core/tokenization/morphology/index.d.ts +5 -0
  58. package/dist/core/tokenization/morphology/index.d.ts.map +1 -0
  59. package/dist/core/tokenization/morphology/types.d.ts +110 -0
  60. package/dist/core/tokenization/morphology/types.d.ts.map +1 -0
  61. package/dist/core/tokenization/token-utils.d.ts +111 -0
  62. package/dist/core/tokenization/token-utils.d.ts.map +1 -0
  63. package/dist/core/types.d.ts +382 -0
  64. package/dist/core/types.d.ts.map +1 -0
  65. package/dist/core/types.js +108 -0
  66. package/dist/core/types.js.map +1 -0
  67. package/dist/generation/diagnostics.d.ts +120 -0
  68. package/dist/generation/diagnostics.d.ts.map +1 -0
  69. package/dist/generation/index.d.ts +7 -0
  70. package/dist/generation/index.d.ts.map +1 -0
  71. package/dist/generation/index.js +339 -0
  72. package/dist/generation/index.js.map +1 -0
  73. package/dist/generation/pattern-generator.d.ts +48 -0
  74. package/dist/generation/pattern-generator.d.ts.map +1 -0
  75. package/dist/generation/renderer.d.ts +115 -0
  76. package/dist/generation/renderer.d.ts.map +1 -0
  77. package/dist/grammar/index.d.ts +10 -0
  78. package/dist/grammar/index.d.ts.map +1 -0
  79. package/dist/grammar/index.js +391 -0
  80. package/dist/grammar/index.js.map +1 -0
  81. package/dist/grammar/transformer.d.ts +56 -0
  82. package/dist/grammar/transformer.d.ts.map +1 -0
  83. package/dist/grammar/types.d.ts +236 -0
  84. package/dist/grammar/types.d.ts.map +1 -0
  85. package/dist/index.cjs +4454 -0
  86. package/dist/index.cjs.map +1 -0
  87. package/dist/index.d.ts +46 -0
  88. package/dist/index.d.ts.map +1 -0
  89. package/dist/index.js +4336 -0
  90. package/dist/index.js.map +1 -0
  91. package/dist/interfaces/dictionary.d.ts +82 -0
  92. package/dist/interfaces/dictionary.d.ts.map +1 -0
  93. package/dist/interfaces/index.d.ts +10 -0
  94. package/dist/interfaces/index.d.ts.map +1 -0
  95. package/dist/interfaces/profile-provider.d.ts +67 -0
  96. package/dist/interfaces/profile-provider.d.ts.map +1 -0
  97. package/dist/interfaces/value-extractor.d.ts +168 -0
  98. package/dist/interfaces/value-extractor.d.ts.map +1 -0
  99. package/dist/multilingual/index.d.ts +8 -0
  100. package/dist/multilingual/index.d.ts.map +1 -0
  101. package/dist/multilingual/index.js +1 -0
  102. package/dist/multilingual/index.js.map +1 -0
  103. package/dist/parsing/index.d.ts +8 -0
  104. package/dist/parsing/index.d.ts.map +1 -0
  105. package/dist/parsing/index.js +1415 -0
  106. package/dist/parsing/index.js.map +1 -0
  107. package/dist/parsing/multi-statement.d.ts +265 -0
  108. package/dist/parsing/multi-statement.d.ts.map +1 -0
  109. package/dist/schema/command-schema.d.ts +78 -0
  110. package/dist/schema/command-schema.d.ts.map +1 -0
  111. package/dist/schema/index.d.ts +5 -0
  112. package/dist/schema/index.d.ts.map +1 -0
  113. package/dist/schema/index.js +25 -0
  114. package/dist/schema/index.js.map +1 -0
  115. package/dist/test-setup.d.ts +9 -0
  116. package/dist/test-setup.d.ts.map +1 -0
  117. package/dist/testing/index.d.ts +50 -0
  118. package/dist/testing/index.d.ts.map +1 -0
  119. package/dist/testing/index.js +16969 -0
  120. package/dist/testing/index.js.map +1 -0
  121. package/package.json +122 -0
  122. package/src/__test__/fixtures/sql-dsl.ts +232 -0
  123. package/src/__test__/sql-integration.test.ts +189 -0
  124. package/src/__test__/test-utils.ts +260 -0
  125. package/src/aot/aot-orchestrator.test.ts +413 -0
  126. package/src/aot/aot-orchestrator.ts +238 -0
  127. package/src/aot/domain-scanner.ts +178 -0
  128. package/src/aot/index.ts +8 -0
  129. package/src/aot/types.ts +124 -0
  130. package/src/api/create-dsl.ts +367 -0
  131. package/src/api/dispatcher.test.ts +336 -0
  132. package/src/api/dispatcher.ts +222 -0
  133. package/src/api/domain-registry.test.ts +336 -0
  134. package/src/api/domain-registry.ts +500 -0
  135. package/src/api/index.ts +7 -0
  136. package/src/core/index.ts +7 -0
  137. package/src/core/logger.ts +130 -0
  138. package/src/core/pattern-matching/index.ts +6 -0
  139. package/src/core/pattern-matching/pattern-matcher.test.ts +900 -0
  140. package/src/core/pattern-matching/pattern-matcher.ts +1548 -0
  141. package/src/core/pattern-matching/pattern-matcher.ts.backup +1267 -0
  142. package/src/core/pattern-matching/utils/index.ts +6 -0
  143. package/src/core/pattern-matching/utils/possessive-keywords.ts +55 -0
  144. package/src/core/pattern-matching/utils/type-validation.test.ts +316 -0
  145. package/src/core/pattern-matching/utils/type-validation.ts +134 -0
  146. package/src/core/tokenization/base-tokenizer.ts +916 -0
  147. package/src/core/tokenization/char-classifiers.ts +79 -0
  148. package/src/core/tokenization/create-simple-tokenizer.test.ts +260 -0
  149. package/src/core/tokenization/default-extractors.ts +69 -0
  150. package/src/core/tokenization/extractors/index.ts +9 -0
  151. package/src/core/tokenization/extractors/operator.ts +75 -0
  152. package/src/core/tokenization/extractors/punctuation.ts +39 -0
  153. package/src/core/tokenization/extractors.ts +452 -0
  154. package/src/core/tokenization/index.ts +11 -0
  155. package/src/core/tokenization/morphology/index.ts +5 -0
  156. package/src/core/tokenization/morphology/types.ts +211 -0
  157. package/src/core/tokenization/token-utils.ts +252 -0
  158. package/src/core/types.ts +589 -0
  159. package/src/generation/diagnostics.test.ts +171 -0
  160. package/src/generation/diagnostics.ts +239 -0
  161. package/src/generation/index.ts +7 -0
  162. package/src/generation/pattern-generator.test.ts +430 -0
  163. package/src/generation/pattern-generator.ts +315 -0
  164. package/src/generation/renderer.test.ts +266 -0
  165. package/src/generation/renderer.ts +244 -0
  166. package/src/grammar/index.ts +12 -0
  167. package/src/grammar/transformer.ts +159 -0
  168. package/src/grammar/types.ts +630 -0
  169. package/src/index.ts +157 -0
  170. package/src/interfaces/dictionary.ts +123 -0
  171. package/src/interfaces/index.ts +10 -0
  172. package/src/interfaces/profile-provider.ts +88 -0
  173. package/src/interfaces/value-extractor.ts +435 -0
  174. package/src/multilingual/index.ts +9 -0
  175. package/src/parsing/index.ts +27 -0
  176. package/src/parsing/multi-statement.test.ts +480 -0
  177. package/src/parsing/multi-statement.ts +648 -0
  178. package/src/schema/command-schema.ts +118 -0
  179. package/src/schema/index.ts +5 -0
  180. package/src/test-setup.ts +45 -0
  181. package/src/testing/index.ts +137 -0
package/package.json ADDED
@@ -0,0 +1,122 @@
1
+ {
2
+ "name": "@lokascript/framework",
3
+ "version": "2.0.0",
4
+ "description": "Generic framework for building multilingual DSLs with semantic parsing and grammar transformation",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ },
15
+ "./core": {
16
+ "types": "./dist/core/index.d.ts",
17
+ "import": "./dist/core/index.js"
18
+ },
19
+ "./core/types": {
20
+ "types": "./dist/core/types.d.ts",
21
+ "import": "./dist/core/types.js"
22
+ },
23
+ "./core/tokenization": {
24
+ "types": "./dist/core/tokenization/index.d.ts",
25
+ "import": "./dist/core/tokenization/index.js"
26
+ },
27
+ "./core/pattern-matching": {
28
+ "types": "./dist/core/pattern-matching/index.d.ts",
29
+ "import": "./dist/core/pattern-matching/index.js"
30
+ },
31
+ "./multilingual": {
32
+ "types": "./dist/multilingual/index.d.ts",
33
+ "import": "./dist/multilingual/index.js"
34
+ },
35
+ "./schema": {
36
+ "types": "./dist/schema/index.d.ts",
37
+ "import": "./dist/schema/index.js"
38
+ },
39
+ "./parsing": {
40
+ "types": "./dist/parsing/index.d.ts",
41
+ "import": "./dist/parsing/index.js"
42
+ },
43
+ "./generation": {
44
+ "types": "./dist/generation/index.d.ts",
45
+ "import": "./dist/generation/index.js"
46
+ },
47
+ "./grammar": {
48
+ "types": "./dist/grammar/index.d.ts",
49
+ "import": "./dist/grammar/index.js"
50
+ },
51
+ "./api": {
52
+ "types": "./dist/api/index.d.ts",
53
+ "import": "./dist/api/index.js"
54
+ },
55
+ "./testing": {
56
+ "types": "./dist/testing/index.d.ts",
57
+ "import": "./dist/testing/index.js"
58
+ }
59
+ },
60
+ "scripts": {
61
+ "build": "tsup && npm run build:types",
62
+ "build:types": "tsc --emitDeclarationOnly --outDir dist",
63
+ "dev": "tsup --watch",
64
+ "test": "vitest",
65
+ "test:run": "vitest run",
66
+ "test:watch": "vitest watch",
67
+ "test:check": "vitest run --reporter=dot 2>&1 | tail -5",
68
+ "test:coverage": "vitest run --coverage",
69
+ "test:unit": "vitest run src/core/**/*.test.ts src/generation/**/*.test.ts",
70
+ "test:integration": "vitest run src/__test__/**/*.test.ts src/api/**/*.test.ts",
71
+ "test:pattern-matcher": "vitest run src/core/pattern-matching",
72
+ "test:tokenizer": "vitest run src/core/tokenization",
73
+ "typecheck": "tsc --noEmit",
74
+ "create-domain": "npx tsx scripts/create-domain.ts"
75
+ },
76
+ "keywords": [
77
+ "dsl",
78
+ "multilingual",
79
+ "i18n",
80
+ "parser",
81
+ "semantic",
82
+ "pattern-matching",
83
+ "grammar-transformation",
84
+ "natural-language",
85
+ "framework",
86
+ "compiler",
87
+ "svo",
88
+ "sov",
89
+ "vso"
90
+ ],
91
+ "author": "LokaScript Contributors",
92
+ "license": "MIT",
93
+ "devDependencies": {
94
+ "@types/node": "^20.0.0",
95
+ "@vitest/coverage-v8": "^4.0.17",
96
+ "tsup": "^8.0.0",
97
+ "typescript": "^5.0.0",
98
+ "vitest": "^4.0.0"
99
+ },
100
+ "files": [
101
+ "dist",
102
+ "src",
103
+ "LICENSE",
104
+ "README.md"
105
+ ],
106
+ "repository": {
107
+ "type": "git",
108
+ "url": "git+https://github.com/codetalcott/hyperfixi.git",
109
+ "directory": "packages/framework"
110
+ },
111
+ "bugs": {
112
+ "url": "https://github.com/codetalcott/hyperfixi/issues"
113
+ },
114
+ "homepage": "https://github.com/codetalcott/hyperfixi/tree/main/packages/framework#readme",
115
+ "engines": {
116
+ "node": ">=18.0.0"
117
+ },
118
+ "publishConfig": {
119
+ "access": "public"
120
+ },
121
+ "sideEffects": false
122
+ }
@@ -0,0 +1,232 @@
1
+ /**
2
+ * SQL DSL Test Fixture
3
+ *
4
+ * Reusable SQL DSL configuration for integration tests.
5
+ * Based on examples/sql-dsl/simple-sql.ts
6
+ */
7
+
8
+ import {
9
+ createMultilingualDSL,
10
+ defineCommand,
11
+ defineRole,
12
+ BaseTokenizer,
13
+ getDefaultExtractors,
14
+ type SemanticNode,
15
+ type MultilingualDSL,
16
+ } from '../../index';
17
+
18
+ // =============================================================================
19
+ // Command Schemas
20
+ // =============================================================================
21
+
22
+ export const selectCommand = defineCommand({
23
+ action: 'select',
24
+ description: 'Retrieve data from a table',
25
+ category: 'query',
26
+ primaryRole: 'columns',
27
+ roles: [
28
+ defineRole({
29
+ role: 'columns',
30
+ description: 'Columns to select',
31
+ required: true,
32
+ expectedTypes: ['expression'],
33
+ svoPosition: 2,
34
+ sovPosition: 1,
35
+ }),
36
+ defineRole({
37
+ role: 'source',
38
+ description: 'Table to select from',
39
+ required: true,
40
+ expectedTypes: ['expression'],
41
+ svoPosition: 1,
42
+ sovPosition: 2,
43
+ markerOverride: { en: 'from', es: 'de' },
44
+ }),
45
+ defineRole({
46
+ role: 'condition',
47
+ description: 'WHERE clause condition',
48
+ required: false,
49
+ expectedTypes: ['expression'],
50
+ svoPosition: 0,
51
+ sovPosition: 0,
52
+ markerOverride: { en: 'where', es: 'donde' },
53
+ }),
54
+ ],
55
+ });
56
+
57
+ // =============================================================================
58
+ // Tokenizers
59
+ // =============================================================================
60
+
61
+ class EnglishSQLTokenizer extends BaseTokenizer {
62
+ readonly language = 'en';
63
+ readonly direction = 'ltr' as const;
64
+
65
+ constructor() {
66
+ super();
67
+ this.registerExtractors(getDefaultExtractors());
68
+ }
69
+
70
+ classifyToken(token: string): 'keyword' | 'identifier' | 'literal' | 'operator' {
71
+ const keywords = ['select', 'from', 'where'];
72
+ if (keywords.includes(token.toLowerCase())) {
73
+ return 'keyword';
74
+ }
75
+ if (/^\d+$/.test(token)) {
76
+ return 'literal';
77
+ }
78
+ if (['>', '<', '=', '>=', '<=', '!='].includes(token)) {
79
+ return 'operator';
80
+ }
81
+ return 'identifier';
82
+ }
83
+ }
84
+
85
+ class SpanishSQLTokenizer extends BaseTokenizer {
86
+ readonly language = 'es';
87
+ readonly direction = 'ltr' as const;
88
+
89
+ constructor() {
90
+ super();
91
+ this.registerExtractors(getDefaultExtractors());
92
+ }
93
+
94
+ classifyToken(token: string): 'keyword' | 'identifier' | 'literal' | 'operator' {
95
+ const keywords = ['seleccionar', 'de', 'donde'];
96
+ if (keywords.includes(token.toLowerCase())) {
97
+ return 'keyword';
98
+ }
99
+ if (/^\d+$/.test(token)) {
100
+ return 'literal';
101
+ }
102
+ if (['>', '<', '=', '>=', '<=', '!='].includes(token)) {
103
+ return 'operator';
104
+ }
105
+ return 'identifier';
106
+ }
107
+ }
108
+
109
+ // =============================================================================
110
+ // Language Profiles
111
+ // =============================================================================
112
+
113
+ const englishProfile = {
114
+ code: 'en',
115
+ wordOrder: 'SVO' as const,
116
+ keywords: {
117
+ select: { primary: 'select' },
118
+ },
119
+ roleMarkers: {
120
+ source: { primary: 'from', position: 'before' as const },
121
+ condition: { primary: 'where', position: 'before' as const },
122
+ },
123
+ };
124
+
125
+ const spanishProfile = {
126
+ code: 'es',
127
+ wordOrder: 'SVO' as const,
128
+ keywords: {
129
+ select: { primary: 'seleccionar' },
130
+ },
131
+ roleMarkers: {
132
+ source: { primary: 'de', position: 'before' as const },
133
+ condition: { primary: 'donde', position: 'before' as const },
134
+ },
135
+ };
136
+
137
+ // =============================================================================
138
+ // Code Generator
139
+ // =============================================================================
140
+
141
+ function generateSQL(node: SemanticNode): string {
142
+ if (node.action === 'select') {
143
+ const columns = node.roles.get('columns');
144
+ const source = node.roles.get('source');
145
+ const condition = node.roles.get('condition');
146
+
147
+ const columnsValue = columns ? ('raw' in columns ? columns.raw : columns.value) : '*';
148
+ const sourceValue = source ? ('raw' in source ? source.raw : source.value) : 'table';
149
+
150
+ let sql = `SELECT ${columnsValue} FROM ${sourceValue}`;
151
+
152
+ if (condition) {
153
+ const conditionValue = 'raw' in condition ? condition.raw : condition.value;
154
+ sql += ` WHERE ${conditionValue}`;
155
+ }
156
+
157
+ return sql;
158
+ }
159
+
160
+ throw new Error(`Unknown SQL command: ${node.action}`);
161
+ }
162
+
163
+ // =============================================================================
164
+ // SQL DSL Instance
165
+ // =============================================================================
166
+
167
+ export function createSQLDSL(): MultilingualDSL {
168
+ return createMultilingualDSL({
169
+ name: 'SQL DSL Test Fixture',
170
+ schemas: [selectCommand],
171
+ languages: [
172
+ {
173
+ code: 'en',
174
+ name: 'English',
175
+ nativeName: 'English',
176
+ tokenizer: new EnglishSQLTokenizer(),
177
+ patternProfile: englishProfile,
178
+ },
179
+ {
180
+ code: 'es',
181
+ name: 'Spanish',
182
+ nativeName: 'Español',
183
+ tokenizer: new SpanishSQLTokenizer(),
184
+ patternProfile: spanishProfile,
185
+ },
186
+ ],
187
+ codeGenerator: {
188
+ generate: generateSQL,
189
+ },
190
+ });
191
+ }
192
+
193
+ // =============================================================================
194
+ // Test Data
195
+ // =============================================================================
196
+
197
+ export const testQueries = {
198
+ english: {
199
+ simple: 'select name from users',
200
+ withWhere: 'select name from users where age > 18',
201
+ multipleColumns: 'select name, email from users',
202
+ },
203
+ spanish: {
204
+ simple: 'seleccionar nombre de usuarios',
205
+ withWhere: 'seleccionar nombre de usuarios donde edad > 18',
206
+ multipleColumns: 'seleccionar nombre, email de usuarios',
207
+ },
208
+ };
209
+
210
+ export const expectedSQL = {
211
+ simple: 'SELECT name FROM users',
212
+ withWhere: 'SELECT name FROM users WHERE age > 18',
213
+ multipleColumns: 'SELECT name, email FROM users',
214
+ };
215
+
216
+ export const expectedNodes = {
217
+ simple: {
218
+ action: 'select',
219
+ roles: {
220
+ columns: 'name',
221
+ source: 'users',
222
+ },
223
+ },
224
+ withWhere: {
225
+ action: 'select',
226
+ roles: {
227
+ columns: 'name',
228
+ source: 'users',
229
+ condition: 'age > 18',
230
+ },
231
+ },
232
+ };
@@ -0,0 +1,189 @@
1
+ /**
2
+ * SQL DSL Integration Tests
3
+ *
4
+ * End-to-end tests using the SQL DSL fixture to validate the full pipeline:
5
+ * Tokenization → Pattern Matching → Semantic Parsing → Code Generation
6
+ */
7
+
8
+ import { describe, it, expect } from 'vitest';
9
+ import { createSQLDSL, testQueries, expectedSQL, expectedNodes } from './fixtures/sql-dsl';
10
+
11
+ describe('SQL DSL Integration', () => {
12
+ let sqlDSL: ReturnType<typeof createSQLDSL>;
13
+
14
+ beforeEach(() => {
15
+ sqlDSL = createSQLDSL();
16
+ });
17
+
18
+ describe('English Queries', () => {
19
+ it('should parse simple SELECT query', () => {
20
+ const result = sqlDSL.parse(testQueries.english.simple, 'en');
21
+
22
+ expect(result.kind).toBe('command');
23
+ expect(result.action).toBe('select');
24
+ expect(result.roles.has('columns')).toBe(true);
25
+ expect(result.roles.has('source')).toBe(true);
26
+
27
+ const columns = result.roles.get('columns');
28
+ const source = result.roles.get('source');
29
+
30
+ if (columns && 'raw' in columns) {
31
+ expect(columns.raw).toBe('name');
32
+ }
33
+ if (source && 'raw' in source) {
34
+ expect(source.raw).toBe('users');
35
+ }
36
+ });
37
+
38
+ it('should parse SELECT with WHERE clause', () => {
39
+ const result = sqlDSL.parse(testQueries.english.withWhere, 'en');
40
+
41
+ expect(result.action).toBe('select');
42
+ expect(result.roles.has('columns')).toBe(true);
43
+ expect(result.roles.has('source')).toBe(true);
44
+ expect(result.roles.has('condition')).toBe(true);
45
+
46
+ const condition = result.roles.get('condition');
47
+ expect(condition).toBeDefined();
48
+ if (condition && 'raw' in condition) {
49
+ // The condition role captures the expression after WHERE
50
+ expect(condition.raw).toBeTruthy();
51
+ }
52
+ });
53
+ });
54
+
55
+ describe('Spanish Queries', () => {
56
+ it('should parse Spanish SELECT query', () => {
57
+ const result = sqlDSL.parse(testQueries.spanish.simple, 'es');
58
+
59
+ expect(result.action).toBe('select');
60
+ expect(result.roles.has('columns')).toBe(true);
61
+ expect(result.roles.has('source')).toBe(true);
62
+
63
+ const columns = result.roles.get('columns');
64
+ const source = result.roles.get('source');
65
+
66
+ if (columns && 'raw' in columns) {
67
+ expect(columns.raw).toBe('nombre');
68
+ }
69
+ if (source && 'raw' in source) {
70
+ expect(source.raw).toBe('usuarios');
71
+ }
72
+ });
73
+
74
+ it('should parse Spanish SELECT with WHERE clause', () => {
75
+ const result = sqlDSL.parse(testQueries.spanish.withWhere, 'es');
76
+
77
+ expect(result.action).toBe('select');
78
+ expect(result.roles.has('condition')).toBe(true);
79
+ });
80
+ });
81
+
82
+ describe('Code Generation', () => {
83
+ it('should compile English query to SQL', () => {
84
+ const result = sqlDSL.compile(testQueries.english.simple, 'en');
85
+
86
+ expect(result.ok).toBe(true);
87
+ expect(result.code).toBe(expectedSQL.simple);
88
+ });
89
+
90
+ it('should compile query with WHERE clause', () => {
91
+ const result = sqlDSL.compile(testQueries.english.withWhere, 'en');
92
+
93
+ expect(result.ok).toBe(true);
94
+ // Should contain all parts
95
+ expect(result.code).toContain('SELECT');
96
+ expect(result.code).toContain('FROM');
97
+ expect(result.code).toContain('WHERE');
98
+ });
99
+
100
+ it('should preserve identifiers from source language', () => {
101
+ const enResult = sqlDSL.compile(testQueries.english.simple, 'en');
102
+ const esResult = sqlDSL.compile(testQueries.spanish.simple, 'es');
103
+
104
+ // English uses English identifiers
105
+ expect(enResult.code).toContain('name');
106
+ expect(enResult.code).toContain('users');
107
+
108
+ // Spanish uses Spanish identifiers
109
+ expect(esResult.code).toContain('nombre');
110
+ expect(esResult.code).toContain('usuarios');
111
+
112
+ // But both have SQL structure
113
+ expect(enResult.code).toContain('SELECT');
114
+ expect(esResult.code).toContain('SELECT');
115
+ });
116
+ });
117
+
118
+ describe('Validation', () => {
119
+ it('should validate correct queries', () => {
120
+ const result = sqlDSL.validate(testQueries.english.simple, 'en');
121
+
122
+ expect(result.valid).toBe(true);
123
+ expect(result.node).toBeDefined();
124
+ expect(result.errors).toBeUndefined();
125
+ });
126
+
127
+ it('should handle invalid queries gracefully', () => {
128
+ // Query with unknown keyword
129
+ const result = sqlDSL.validate('invalid query syntax', 'en');
130
+
131
+ expect(result.valid).toBe(false);
132
+ expect(result.errors).toBeDefined();
133
+ expect(result.errors?.length).toBeGreaterThan(0);
134
+ });
135
+ });
136
+
137
+ describe('Multi-language Support', () => {
138
+ it('should support both English and Spanish', () => {
139
+ const languages = sqlDSL.getSupportedLanguages();
140
+
141
+ expect(languages).toContain('en');
142
+ expect(languages).toContain('es');
143
+ expect(languages).toHaveLength(2);
144
+ });
145
+
146
+ it('should reject unsupported language', () => {
147
+ expect(() => {
148
+ sqlDSL.parse('select name from users', 'fr');
149
+ }).toThrow();
150
+ });
151
+ });
152
+
153
+ describe('Semantic Equivalence', () => {
154
+ it('should parse English and Spanish to equivalent semantic structures', () => {
155
+ const enNode = sqlDSL.parse(testQueries.english.simple, 'en');
156
+ const esNode = sqlDSL.parse(testQueries.spanish.simple, 'es');
157
+
158
+ expect(enNode.action).toBe(esNode.action);
159
+ expect(enNode.roles.size).toBe(esNode.roles.size);
160
+ expect(enNode.roles.has('columns')).toBe(esNode.roles.has('columns'));
161
+ expect(enNode.roles.has('source')).toBe(esNode.roles.has('source'));
162
+ });
163
+ });
164
+
165
+ describe('Error Handling', () => {
166
+ it('should handle empty input', () => {
167
+ expect(() => {
168
+ sqlDSL.parse('', 'en');
169
+ }).toThrow();
170
+ });
171
+
172
+ it('should handle whitespace-only input', () => {
173
+ expect(() => {
174
+ sqlDSL.parse(' ', 'en');
175
+ }).toThrow();
176
+ });
177
+
178
+ it('should provide helpful error messages', () => {
179
+ try {
180
+ sqlDSL.parse('unknown command', 'en');
181
+ // Should have thrown
182
+ expect(true).toBe(false);
183
+ } catch (error) {
184
+ expect(error).toBeDefined();
185
+ expect(error instanceof Error).toBe(true);
186
+ }
187
+ });
188
+ });
189
+ });