tjs-lang 0.2.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 (91) hide show
  1. package/CONTEXT.md +594 -0
  2. package/LICENSE +190 -0
  3. package/README.md +220 -0
  4. package/bin/benchmarks.ts +351 -0
  5. package/bin/dev.ts +205 -0
  6. package/bin/docs.js +170 -0
  7. package/bin/install-cursor.sh +71 -0
  8. package/bin/install-vscode.sh +71 -0
  9. package/bin/select-local-models.d.ts +1 -0
  10. package/bin/select-local-models.js +28 -0
  11. package/bin/select-local-models.ts +31 -0
  12. package/demo/autocomplete.test.ts +232 -0
  13. package/demo/docs.json +186 -0
  14. package/demo/examples.test.ts +598 -0
  15. package/demo/index.html +91 -0
  16. package/demo/src/autocomplete.ts +482 -0
  17. package/demo/src/capabilities.ts +859 -0
  18. package/demo/src/demo-nav.ts +2097 -0
  19. package/demo/src/examples.test.ts +161 -0
  20. package/demo/src/examples.ts +476 -0
  21. package/demo/src/imports.test.ts +196 -0
  22. package/demo/src/imports.ts +421 -0
  23. package/demo/src/index.ts +639 -0
  24. package/demo/src/module-store.ts +635 -0
  25. package/demo/src/module-sw.ts +132 -0
  26. package/demo/src/playground.ts +949 -0
  27. package/demo/src/service-host.ts +389 -0
  28. package/demo/src/settings.ts +440 -0
  29. package/demo/src/style.ts +280 -0
  30. package/demo/src/tjs-playground.ts +1605 -0
  31. package/demo/src/ts-examples.ts +478 -0
  32. package/demo/src/ts-playground.ts +1092 -0
  33. package/demo/static/favicon.svg +30 -0
  34. package/demo/static/photo-1.jpg +0 -0
  35. package/demo/static/photo-2.jpg +0 -0
  36. package/demo/static/texts/ai-history.txt +9 -0
  37. package/demo/static/texts/coffee-origins.txt +9 -0
  38. package/demo/static/texts/renewable-energy.txt +9 -0
  39. package/dist/index.js +256 -0
  40. package/dist/index.js.map +37 -0
  41. package/dist/tjs-batteries.js +4 -0
  42. package/dist/tjs-batteries.js.map +15 -0
  43. package/dist/tjs-full.js +256 -0
  44. package/dist/tjs-full.js.map +37 -0
  45. package/dist/tjs-transpiler.js +220 -0
  46. package/dist/tjs-transpiler.js.map +21 -0
  47. package/dist/tjs-vm.js +4 -0
  48. package/dist/tjs-vm.js.map +14 -0
  49. package/docs/CNAME +1 -0
  50. package/docs/favicon.svg +30 -0
  51. package/docs/index.html +91 -0
  52. package/docs/index.js +10468 -0
  53. package/docs/index.js.map +92 -0
  54. package/docs/photo-1.jpg +0 -0
  55. package/docs/photo-1.webp +0 -0
  56. package/docs/photo-2.jpg +0 -0
  57. package/docs/photo-2.webp +0 -0
  58. package/docs/texts/ai-history.txt +9 -0
  59. package/docs/texts/coffee-origins.txt +9 -0
  60. package/docs/texts/renewable-energy.txt +9 -0
  61. package/docs/tjs-lang.svg +31 -0
  62. package/docs/tosijs-agent.svg +31 -0
  63. package/editors/README.md +325 -0
  64. package/editors/ace/ajs-mode.js +328 -0
  65. package/editors/ace/ajs-mode.ts +269 -0
  66. package/editors/ajs-syntax.ts +212 -0
  67. package/editors/build-grammars.ts +510 -0
  68. package/editors/codemirror/ajs-language.js +287 -0
  69. package/editors/codemirror/ajs-language.ts +1447 -0
  70. package/editors/codemirror/autocomplete.test.ts +531 -0
  71. package/editors/codemirror/component.ts +404 -0
  72. package/editors/monaco/ajs-monarch.js +243 -0
  73. package/editors/monaco/ajs-monarch.ts +225 -0
  74. package/editors/tjs-syntax.ts +115 -0
  75. package/editors/vscode/language-configuration.json +37 -0
  76. package/editors/vscode/package.json +65 -0
  77. package/editors/vscode/syntaxes/ajs-injection.tmLanguage.json +107 -0
  78. package/editors/vscode/syntaxes/ajs.tmLanguage.json +252 -0
  79. package/editors/vscode/syntaxes/tjs.tmLanguage.json +333 -0
  80. package/package.json +83 -0
  81. package/src/cli/commands/check.ts +41 -0
  82. package/src/cli/commands/convert.ts +133 -0
  83. package/src/cli/commands/emit.ts +260 -0
  84. package/src/cli/commands/run.ts +68 -0
  85. package/src/cli/commands/test.ts +194 -0
  86. package/src/cli/commands/types.ts +20 -0
  87. package/src/cli/create-app.ts +236 -0
  88. package/src/cli/playground.ts +250 -0
  89. package/src/cli/tjs.ts +166 -0
  90. package/src/cli/tjsx.ts +160 -0
  91. package/tjs-lang.svg +31 -0
@@ -0,0 +1,635 @@
1
+ /**
2
+ * Unified Module Store for Playground Examples
3
+ *
4
+ * Stores user-created modules (both AJS and TJS) in IndexedDB.
5
+ * Modules can be imported by name from other modules.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * const store = await ModuleStore.open()
10
+ *
11
+ * // Save a TJS module
12
+ * await store.save({
13
+ * name: 'utils',
14
+ * type: 'tjs',
15
+ * code: 'export function add(a: 0, b: 0) { return a + b }',
16
+ * description: 'Math utilities'
17
+ * })
18
+ *
19
+ * // Import it from another module
20
+ * // import { add } from 'utils'
21
+ *
22
+ * // Get transpiled code for import resolution
23
+ * const compiled = await store.getCompiled('utils')
24
+ * ```
25
+ */
26
+
27
+ import {
28
+ transpileToJS,
29
+ extractTests,
30
+ testUtils,
31
+ type TJSTranspileResult,
32
+ } from '../../src/lang'
33
+
34
+ // ============================================================================
35
+ // Validation Result
36
+ // ============================================================================
37
+
38
+ export interface ValidationResult {
39
+ valid: boolean
40
+ errors: ValidationError[]
41
+ warnings: string[]
42
+ testResults?: TestResult[]
43
+ }
44
+
45
+ export interface ValidationError {
46
+ type: 'transpile' | 'test' | 'syntax'
47
+ message: string
48
+ line?: number
49
+ column?: number
50
+ }
51
+
52
+ export interface TestResult {
53
+ name: string
54
+ passed: boolean
55
+ error?: string
56
+ }
57
+
58
+ // ============================================================================
59
+ // Types
60
+ // ============================================================================
61
+
62
+ export type ModuleType = 'tjs' | 'ajs'
63
+
64
+ export interface StoredModule {
65
+ /** Unique module name (used for imports) */
66
+ name: string
67
+ /** Module type */
68
+ type: ModuleType
69
+ /** Source code */
70
+ code: string
71
+ /** Optional description */
72
+ description?: string
73
+ /** When created */
74
+ created: number
75
+ /** When last modified */
76
+ modified: number
77
+ /** Version number (increments on save) */
78
+ version: number
79
+ /** Cached transpilation result (for TJS) */
80
+ compiled?: {
81
+ code: string
82
+ version: number
83
+ }
84
+ }
85
+
86
+ export interface ModuleStoreStats {
87
+ total: number
88
+ tjs: number
89
+ ajs: number
90
+ bytes: number
91
+ }
92
+
93
+ // ============================================================================
94
+ // IndexedDB Setup
95
+ // ============================================================================
96
+
97
+ const DB_NAME = 'tjs-modules'
98
+ const DB_VERSION = 1
99
+ const STORE_NAME = 'modules'
100
+
101
+ // ============================================================================
102
+ // ModuleStore Class
103
+ // ============================================================================
104
+
105
+ export class ModuleStore {
106
+ private db: IDBDatabase | null = null
107
+ private static instance: ModuleStore | null = null
108
+
109
+ private constructor() {}
110
+
111
+ /**
112
+ * Get singleton instance
113
+ */
114
+ static async open(): Promise<ModuleStore> {
115
+ if (ModuleStore.instance?.db) {
116
+ return ModuleStore.instance
117
+ }
118
+
119
+ const store = new ModuleStore()
120
+ await store._open()
121
+ ModuleStore.instance = store
122
+ return store
123
+ }
124
+
125
+ private async _open(): Promise<void> {
126
+ return new Promise((resolve, reject) => {
127
+ const request = indexedDB.open(DB_NAME, DB_VERSION)
128
+
129
+ request.onerror = () => reject(request.error)
130
+
131
+ request.onsuccess = () => {
132
+ this.db = request.result
133
+ resolve()
134
+ }
135
+
136
+ request.onupgradeneeded = (event) => {
137
+ const db = (event.target as IDBOpenDBRequest).result
138
+
139
+ if (!db.objectStoreNames.contains(STORE_NAME)) {
140
+ const store = db.createObjectStore(STORE_NAME, { keyPath: 'name' })
141
+ store.createIndex('type', 'type')
142
+ store.createIndex('modified', 'modified')
143
+ }
144
+ }
145
+ })
146
+ }
147
+
148
+ /**
149
+ * Validate a module before saving
150
+ * Checks transpilation and runs inline tests
151
+ */
152
+ async validate(code: string, type: ModuleType): Promise<ValidationResult> {
153
+ const errors: ValidationError[] = []
154
+ const warnings: string[] = []
155
+ let testResults: TestResult[] | undefined
156
+
157
+ if (type === 'tjs') {
158
+ // Step 1: Try to transpile (use 'report' mode to get test results without throwing)
159
+ let transpileResult: TJSTranspileResult
160
+ try {
161
+ transpileResult = transpileToJS(code, { runTests: 'report' })
162
+ if (transpileResult.warnings) {
163
+ warnings.push(...transpileResult.warnings)
164
+ }
165
+ } catch (e: any) {
166
+ // This is a real transpilation error (syntax, parse, etc.)
167
+ errors.push({
168
+ type: 'transpile',
169
+ message: e.message,
170
+ line: e.line,
171
+ column: e.column,
172
+ })
173
+ return { valid: false, errors, warnings }
174
+ }
175
+
176
+ // Step 2: Collect signature test results from transpile
177
+ // Map from transpiler's TestResult (description) to our TestResult (name)
178
+ if (
179
+ transpileResult.testResults &&
180
+ transpileResult.testResults.length > 0
181
+ ) {
182
+ testResults = transpileResult.testResults.map((t) => ({
183
+ name: t.description,
184
+ passed: t.passed,
185
+ error: t.error,
186
+ }))
187
+ }
188
+
189
+ // Step 3: Extract and run explicit test blocks
190
+ const testExtraction = extractTests(code)
191
+ if (testExtraction.tests.length > 0) {
192
+ try {
193
+ const explicitResults = await this.runTests(
194
+ transpileResult.code,
195
+ testExtraction.testRunner
196
+ )
197
+
198
+ // Merge with signature test results
199
+ testResults = testResults
200
+ ? [...testResults, ...explicitResults]
201
+ : explicitResults
202
+
203
+ // Check for failed tests
204
+ const failed = explicitResults.filter((t) => !t.passed)
205
+ if (failed.length > 0) {
206
+ for (const f of failed) {
207
+ errors.push({
208
+ type: 'test',
209
+ message: `Test "${f.name}" failed: ${f.error}`,
210
+ })
211
+ }
212
+ }
213
+ } catch (e: any) {
214
+ errors.push({
215
+ type: 'test',
216
+ message: `Test execution error: ${e.message}`,
217
+ })
218
+ }
219
+ }
220
+ }
221
+
222
+ // AJS validation could be added here (parse with ajs())
223
+
224
+ return {
225
+ valid: errors.length === 0,
226
+ errors,
227
+ warnings,
228
+ testResults,
229
+ }
230
+ }
231
+
232
+ /**
233
+ * Run tests in an isolated context
234
+ */
235
+ private async runTests(
236
+ moduleCode: string,
237
+ testRunnerCode: string
238
+ ): Promise<TestResult[]> {
239
+ // Create isolated execution context
240
+ const fullCode = `
241
+ ${testUtils}
242
+ ${moduleCode}
243
+ ${testRunnerCode}
244
+ `
245
+
246
+ try {
247
+ // Run in async function to handle await in tests
248
+ const runTests = new Function(`
249
+ return (async () => {
250
+ ${fullCode}
251
+ })()
252
+ `)
253
+
254
+ const result = await runTests()
255
+
256
+ if (result?.results) {
257
+ return result.results.map((r: any) => ({
258
+ name: r.description,
259
+ passed: r.passed,
260
+ error: r.error,
261
+ }))
262
+ }
263
+
264
+ return []
265
+ } catch (e: any) {
266
+ throw new Error(`Test execution failed: ${e.message}`)
267
+ }
268
+ }
269
+
270
+ /**
271
+ * Save a module (create or update)
272
+ * Validates the module first - fails if transpilation or tests fail
273
+ */
274
+ async save(
275
+ module: Omit<StoredModule, 'created' | 'modified' | 'version' | 'compiled'>,
276
+ options: { skipValidation?: boolean } = {}
277
+ ): Promise<StoredModule> {
278
+ if (!this.db) throw new Error('Store not open')
279
+
280
+ // Validate first (unless skipped)
281
+ if (!options.skipValidation) {
282
+ const validation = await this.validate(module.code, module.type)
283
+ if (!validation.valid) {
284
+ const errorMessages = validation.errors.map((e) => e.message).join('\n')
285
+ throw new Error(`Module validation failed:\n${errorMessages}`)
286
+ }
287
+ }
288
+
289
+ // Check for existing
290
+ const existing = await this.get(module.name)
291
+
292
+ const now = Date.now()
293
+ const entry: StoredModule = {
294
+ ...module,
295
+ created: existing?.created ?? now,
296
+ modified: now,
297
+ version: (existing?.version ?? 0) + 1,
298
+ }
299
+
300
+ // Pre-compile TJS modules (we know it succeeds because validation passed)
301
+ if (module.type === 'tjs') {
302
+ try {
303
+ const result = transpileToJS(module.code)
304
+ entry.compiled = {
305
+ code: result.code,
306
+ version: entry.version,
307
+ }
308
+ } catch (e) {
309
+ // Should not happen if validation passed, but handle anyway
310
+ console.warn(`Compilation warning for ${module.name}:`, e)
311
+ }
312
+ }
313
+
314
+ return new Promise((resolve, reject) => {
315
+ const tx = this.db!.transaction(STORE_NAME, 'readwrite')
316
+ const store = tx.objectStore(STORE_NAME)
317
+ const request = store.put(entry)
318
+
319
+ request.onerror = () => reject(request.error)
320
+ request.onsuccess = () => resolve(entry)
321
+ })
322
+ }
323
+
324
+ /**
325
+ * Save without validation (use with caution)
326
+ */
327
+ async saveUnsafe(
328
+ module: Omit<StoredModule, 'created' | 'modified' | 'version' | 'compiled'>
329
+ ): Promise<StoredModule> {
330
+ return this.save(module, { skipValidation: true })
331
+ }
332
+
333
+ /**
334
+ * Get a module by name
335
+ */
336
+ async get(name: string): Promise<StoredModule | undefined> {
337
+ if (!this.db) throw new Error('Store not open')
338
+
339
+ return new Promise((resolve, reject) => {
340
+ const tx = this.db!.transaction(STORE_NAME, 'readonly')
341
+ const store = tx.objectStore(STORE_NAME)
342
+ const request = store.get(name)
343
+
344
+ request.onerror = () => reject(request.error)
345
+ request.onsuccess = () => resolve(request.result)
346
+ })
347
+ }
348
+
349
+ /**
350
+ * Get compiled code for a TJS module (for import resolution)
351
+ */
352
+ async getCompiled(name: string): Promise<string | undefined> {
353
+ const module = await this.get(name)
354
+ if (!module) return undefined
355
+
356
+ if (module.type === 'ajs') {
357
+ // AJS modules export their transpiled AST
358
+ return `export default ${JSON.stringify(module.code)}`
359
+ }
360
+
361
+ // Return cached compiled code, or compile on demand
362
+ if (module.compiled?.version === module.version) {
363
+ return module.compiled.code
364
+ }
365
+
366
+ // Recompile if needed
367
+ try {
368
+ const result = transpileToJS(module.code)
369
+ // Update cache
370
+ await this.save(module)
371
+ return result.code
372
+ } catch (e: any) {
373
+ throw new Error(`Failed to compile module '${name}': ${e.message}`)
374
+ }
375
+ }
376
+
377
+ /**
378
+ * Delete a module
379
+ */
380
+ async delete(name: string): Promise<void> {
381
+ if (!this.db) throw new Error('Store not open')
382
+
383
+ return new Promise((resolve, reject) => {
384
+ const tx = this.db!.transaction(STORE_NAME, 'readwrite')
385
+ const store = tx.objectStore(STORE_NAME)
386
+ const request = store.delete(name)
387
+
388
+ request.onerror = () => reject(request.error)
389
+ request.onsuccess = () => resolve()
390
+ })
391
+ }
392
+
393
+ /**
394
+ * List all modules
395
+ */
396
+ async list(type?: ModuleType): Promise<StoredModule[]> {
397
+ if (!this.db) throw new Error('Store not open')
398
+
399
+ return new Promise((resolve, reject) => {
400
+ const tx = this.db!.transaction(STORE_NAME, 'readonly')
401
+ const store = tx.objectStore(STORE_NAME)
402
+
403
+ let request: IDBRequest
404
+ if (type) {
405
+ const index = store.index('type')
406
+ request = index.getAll(type)
407
+ } else {
408
+ request = store.getAll()
409
+ }
410
+
411
+ request.onerror = () => reject(request.error)
412
+ request.onsuccess = () => {
413
+ const modules = request.result as StoredModule[]
414
+ // Sort by modified desc
415
+ modules.sort((a, b) => b.modified - a.modified)
416
+ resolve(modules)
417
+ }
418
+ })
419
+ }
420
+
421
+ /**
422
+ * Check if a module exists
423
+ */
424
+ async exists(name: string): Promise<boolean> {
425
+ const module = await this.get(name)
426
+ return module !== undefined
427
+ }
428
+
429
+ /**
430
+ * Rename a module
431
+ */
432
+ async rename(
433
+ oldName: string,
434
+ newName: string
435
+ ): Promise<StoredModule | undefined> {
436
+ const module = await this.get(oldName)
437
+ if (!module) return undefined
438
+
439
+ // Check new name isn't taken
440
+ if (await this.exists(newName)) {
441
+ throw new Error(`Module '${newName}' already exists`)
442
+ }
443
+
444
+ // Delete old, create new
445
+ await this.delete(oldName)
446
+ return this.save({ ...module, name: newName })
447
+ }
448
+
449
+ /**
450
+ * Get store statistics
451
+ */
452
+ async getStats(): Promise<ModuleStoreStats> {
453
+ const modules = await this.list()
454
+
455
+ let bytes = 0
456
+ let tjs = 0
457
+ let ajs = 0
458
+
459
+ for (const m of modules) {
460
+ bytes += m.code.length * 2 // UTF-16
461
+ if (m.compiled) bytes += m.compiled.code.length * 2
462
+ if (m.type === 'tjs') tjs++
463
+ else ajs++
464
+ }
465
+
466
+ return { total: modules.length, tjs, ajs, bytes }
467
+ }
468
+
469
+ /**
470
+ * Clear all modules
471
+ */
472
+ async clear(): Promise<void> {
473
+ if (!this.db) throw new Error('Store not open')
474
+
475
+ return new Promise((resolve, reject) => {
476
+ const tx = this.db!.transaction(STORE_NAME, 'readwrite')
477
+ const store = tx.objectStore(STORE_NAME)
478
+ const request = store.clear()
479
+
480
+ request.onerror = () => reject(request.error)
481
+ request.onsuccess = () => resolve()
482
+ })
483
+ }
484
+
485
+ /**
486
+ * Export all modules as JSON
487
+ */
488
+ async export(): Promise<string> {
489
+ const modules = await this.list()
490
+ return JSON.stringify(modules, null, 2)
491
+ }
492
+
493
+ /**
494
+ * Import modules from JSON
495
+ */
496
+ async import(json: string, overwrite = false): Promise<number> {
497
+ const modules = JSON.parse(json) as StoredModule[]
498
+ let imported = 0
499
+
500
+ for (const m of modules) {
501
+ if (!overwrite && (await this.exists(m.name))) {
502
+ continue
503
+ }
504
+ await this.save({
505
+ name: m.name,
506
+ type: m.type,
507
+ code: m.code,
508
+ description: m.description,
509
+ })
510
+ imported++
511
+ }
512
+
513
+ return imported
514
+ }
515
+
516
+ /**
517
+ * Get module names for import resolution
518
+ */
519
+ async getModuleNames(): Promise<string[]> {
520
+ const modules = await this.list()
521
+ return modules.map((m) => m.name)
522
+ }
523
+
524
+ /**
525
+ * Close the database connection
526
+ */
527
+ close(): void {
528
+ if (this.db) {
529
+ this.db.close()
530
+ this.db = null
531
+ ModuleStore.instance = null
532
+ }
533
+ }
534
+ }
535
+
536
+ // ============================================================================
537
+ // Import Resolution
538
+ // ============================================================================
539
+
540
+ /**
541
+ * Check if an import specifier refers to a local module
542
+ */
543
+ export async function isLocalModule(specifier: string): Promise<boolean> {
544
+ // Local modules are bare specifiers that exist in the store
545
+ if (
546
+ specifier.startsWith('.') ||
547
+ specifier.startsWith('/') ||
548
+ specifier.includes('://')
549
+ ) {
550
+ return false
551
+ }
552
+
553
+ const store = await ModuleStore.open()
554
+ return store.exists(specifier)
555
+ }
556
+
557
+ /**
558
+ * Resolve local module imports in source code
559
+ * Returns a blob URL that can be used in import map
560
+ */
561
+ export async function resolveLocalModule(name: string): Promise<string> {
562
+ const store = await ModuleStore.open()
563
+ const code = await store.getCompiled(name)
564
+
565
+ if (!code) {
566
+ throw new Error(`Local module '${name}' not found`)
567
+ }
568
+
569
+ // Create a blob URL for the compiled code
570
+ const blob = new Blob([code], { type: 'application/javascript' })
571
+ return URL.createObjectURL(blob)
572
+ }
573
+
574
+ /**
575
+ * Build import map entries for all local modules referenced in source
576
+ */
577
+ export async function resolveLocalImports(
578
+ specifiers: string[]
579
+ ): Promise<Record<string, string>> {
580
+ const imports: Record<string, string> = {}
581
+ const store = await ModuleStore.open()
582
+ const localNames = await store.getModuleNames()
583
+
584
+ for (const spec of specifiers) {
585
+ if (localNames.includes(spec)) {
586
+ imports[spec] = await resolveLocalModule(spec)
587
+ }
588
+ }
589
+
590
+ return imports
591
+ }
592
+
593
+ // ============================================================================
594
+ // Migration from localStorage
595
+ // ============================================================================
596
+
597
+ /**
598
+ * Migrate existing localStorage examples to IndexedDB
599
+ */
600
+ export async function migrateFromLocalStorage(): Promise<number> {
601
+ const store = await ModuleStore.open()
602
+ let migrated = 0
603
+
604
+ // Migrate AJS examples
605
+ const ajsKey = 'agent-playground-examples'
606
+ const ajsStored = localStorage.getItem(ajsKey)
607
+ if (ajsStored) {
608
+ try {
609
+ const examples = JSON.parse(ajsStored) as Array<{
610
+ name: string
611
+ code: string
612
+ description?: string
613
+ }>
614
+ for (const ex of examples) {
615
+ if (!(await store.exists(ex.name))) {
616
+ await store.save({
617
+ name: ex.name,
618
+ type: 'ajs',
619
+ code: ex.code,
620
+ description: ex.description,
621
+ })
622
+ migrated++
623
+ }
624
+ }
625
+ // Don't delete localStorage yet - keep as backup
626
+ // localStorage.removeItem(ajsKey)
627
+ } catch (e) {
628
+ console.warn('Failed to migrate AJS examples:', e)
629
+ }
630
+ }
631
+
632
+ // TJS playground didn't have localStorage persistence, so nothing to migrate
633
+
634
+ return migrated
635
+ }