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,389 @@
1
+ /**
2
+ * Service Host - Run TJS modules as sandboxed services
3
+ *
4
+ * This enables full-stack development in the browser:
5
+ * - Define a TJS module with exported functions
6
+ * - "Deploy" it as a service
7
+ * - Call it from other modules via typed client
8
+ *
9
+ * The service runs sandboxed with:
10
+ * - Input validation (TJS runtime)
11
+ * - Fuel metering (bounded execution)
12
+ * - Capability injection (controlled IO)
13
+ *
14
+ * @example
15
+ * ```typescript
16
+ * // Define service
17
+ * const host = new ServiceHost()
18
+ * await host.deploy('users', userServiceCode)
19
+ *
20
+ * // Call from client
21
+ * const result = await host.call('users', 'createUser', { name: 'Alice' })
22
+ * ```
23
+ */
24
+
25
+ import { transpileToJS, installRuntime } from '../../src/lang'
26
+ import { ModuleStore } from './module-store'
27
+
28
+ // Ensure TJS runtime is available
29
+ installRuntime()
30
+
31
+ // ============================================================================
32
+ // Types
33
+ // ============================================================================
34
+
35
+ export interface ServiceEndpoint {
36
+ name: string
37
+ params: Record<string, { type: string; required: boolean }>
38
+ returns?: { type: string }
39
+ }
40
+
41
+ export interface DeployedService {
42
+ name: string
43
+ code: string
44
+ compiled: string
45
+ endpoints: ServiceEndpoint[]
46
+ instance: Record<string, Function>
47
+ fuel: number
48
+ calls: number
49
+ }
50
+
51
+ export interface CallResult<T = any> {
52
+ success: boolean
53
+ result?: T
54
+ error?: { message: string; path?: string }
55
+ fuel: number
56
+ duration: number
57
+ }
58
+
59
+ // ============================================================================
60
+ // ServiceHost
61
+ // ============================================================================
62
+
63
+ export class ServiceHost {
64
+ private services = new Map<string, DeployedService>()
65
+ private defaultFuel = 1000
66
+
67
+ /**
68
+ * Deploy a TJS module as a service
69
+ */
70
+ async deploy(name: string, code: string): Promise<DeployedService> {
71
+ // Transpile TJS to JS
72
+ const result = transpileToJS(code)
73
+
74
+ // Extract endpoint metadata from __tjs annotations
75
+ const endpoints = this.extractEndpoints(result.code)
76
+
77
+ // Create sandboxed instance
78
+ const instance = this.createInstance(result.code)
79
+
80
+ const service: DeployedService = {
81
+ name,
82
+ code,
83
+ compiled: result.code,
84
+ endpoints,
85
+ instance,
86
+ fuel: this.defaultFuel,
87
+ calls: 0,
88
+ }
89
+
90
+ this.services.set(name, service)
91
+ return service
92
+ }
93
+
94
+ /**
95
+ * Deploy a module from the store
96
+ */
97
+ async deployFromStore(name: string): Promise<DeployedService> {
98
+ const store = await ModuleStore.open()
99
+ const module = await store.get(name)
100
+
101
+ if (!module) {
102
+ throw new Error(`Module '${name}' not found in store`)
103
+ }
104
+
105
+ if (module.type !== 'tjs') {
106
+ throw new Error(`Module '${name}' is not a TJS module`)
107
+ }
108
+
109
+ return this.deploy(name, module.code)
110
+ }
111
+
112
+ /**
113
+ * Call a service endpoint
114
+ */
115
+ async call<T = any>(
116
+ serviceName: string,
117
+ endpoint: string,
118
+ args: any
119
+ ): Promise<CallResult<T>> {
120
+ const start = performance.now()
121
+ const service = this.services.get(serviceName)
122
+
123
+ if (!service) {
124
+ return {
125
+ success: false,
126
+ error: { message: `Service '${serviceName}' not deployed` },
127
+ fuel: 0,
128
+ duration: performance.now() - start,
129
+ }
130
+ }
131
+
132
+ const fn = service.instance[endpoint]
133
+ if (typeof fn !== 'function') {
134
+ return {
135
+ success: false,
136
+ error: {
137
+ message: `Endpoint '${endpoint}' not found in '${serviceName}'`,
138
+ },
139
+ fuel: 0,
140
+ duration: performance.now() - start,
141
+ }
142
+ }
143
+
144
+ service.calls++
145
+
146
+ try {
147
+ // Call with TJS validation (built into the transpiled code)
148
+ const result = fn(args)
149
+
150
+ // Check for monadic error
151
+ if (result && result.$error) {
152
+ return {
153
+ success: false,
154
+ error: { message: result.message, path: result.path },
155
+ fuel: 1, // minimal fuel for failed validation
156
+ duration: performance.now() - start,
157
+ }
158
+ }
159
+
160
+ return {
161
+ success: true,
162
+ result,
163
+ fuel: 1, // TODO: actual fuel metering
164
+ duration: performance.now() - start,
165
+ }
166
+ } catch (e: any) {
167
+ return {
168
+ success: false,
169
+ error: { message: e.message },
170
+ fuel: 1,
171
+ duration: performance.now() - start,
172
+ }
173
+ }
174
+ }
175
+
176
+ /**
177
+ * Get service info
178
+ */
179
+ getService(name: string): DeployedService | undefined {
180
+ return this.services.get(name)
181
+ }
182
+
183
+ /**
184
+ * List all deployed services
185
+ */
186
+ listServices(): string[] {
187
+ return [...this.services.keys()]
188
+ }
189
+
190
+ /**
191
+ * Get endpoints for a service
192
+ */
193
+ getEndpoints(name: string): ServiceEndpoint[] {
194
+ return this.services.get(name)?.endpoints ?? []
195
+ }
196
+
197
+ /**
198
+ * Undeploy a service
199
+ */
200
+ undeploy(name: string): boolean {
201
+ return this.services.delete(name)
202
+ }
203
+
204
+ /**
205
+ * Create a typed client for a service
206
+ * Returns a proxy that calls the service
207
+ */
208
+ createClient<T extends Record<string, Function>>(serviceName: string): T {
209
+ const host = this
210
+
211
+ return new Proxy({} as T, {
212
+ get(_, prop: string) {
213
+ return async (args: any) => {
214
+ const result = await host.call(serviceName, prop, args)
215
+ if (!result.success) {
216
+ throw new Error(result.error?.message ?? 'Service call failed')
217
+ }
218
+ return result.result
219
+ }
220
+ },
221
+ })
222
+ }
223
+
224
+ /**
225
+ * Generate client code that can be imported
226
+ * Creates a module with typed function stubs
227
+ */
228
+ generateClientModule(serviceName: string): string {
229
+ const service = this.services.get(serviceName)
230
+ if (!service) {
231
+ throw new Error(`Service '${serviceName}' not deployed`)
232
+ }
233
+
234
+ const lines = [
235
+ `// Auto-generated client for ${serviceName}`,
236
+ `// Calls are proxied through ServiceHost`,
237
+ ``,
238
+ ]
239
+
240
+ for (const ep of service.endpoints) {
241
+ const paramStr = Object.entries(ep.params)
242
+ .map(([name, info]) => `${name}`)
243
+ .join(', ')
244
+
245
+ lines.push(`export async function ${ep.name}(${paramStr || 'args'}) {`)
246
+ lines.push(
247
+ ` return globalThis.__serviceHost.call('${serviceName}', '${
248
+ ep.name
249
+ }', ${paramStr || 'args'})`
250
+ )
251
+ lines.push(`}`)
252
+ lines.push(``)
253
+ }
254
+
255
+ return lines.join('\n')
256
+ }
257
+
258
+ // ============================================================================
259
+ // Private Methods
260
+ // ============================================================================
261
+
262
+ private extractEndpoints(code: string): ServiceEndpoint[] {
263
+ const endpoints: ServiceEndpoint[] = []
264
+
265
+ // Find all functions with __tjs metadata
266
+ // Pattern: functionName.__tjs = { params: ..., returns: ... }
267
+ const metadataPattern =
268
+ /(\w+)\.__tjs\s*=\s*(\{[\s\S]*?\})\s*(?=\w+\.__tjs|$)/g
269
+
270
+ let match
271
+ while ((match = metadataPattern.exec(code)) !== null) {
272
+ const name = match[1]
273
+ try {
274
+ // Safe eval of the metadata object
275
+ const metadata = Function(`return ${match[2]}`)()
276
+
277
+ const params: Record<string, { type: string; required: boolean }> = {}
278
+ if (metadata.params) {
279
+ for (const [pname, pinfo] of Object.entries(metadata.params as any)) {
280
+ params[pname] = {
281
+ type: (pinfo as any).type?.kind ?? 'any',
282
+ required: (pinfo as any).required ?? true,
283
+ }
284
+ }
285
+ }
286
+
287
+ endpoints.push({
288
+ name,
289
+ params,
290
+ returns: metadata.returns
291
+ ? { type: metadata.returns.kind ?? 'any' }
292
+ : undefined,
293
+ })
294
+ } catch (e) {
295
+ console.warn(`Failed to parse metadata for ${name}:`, e)
296
+ }
297
+ }
298
+
299
+ // Also find exported functions without explicit __tjs (bare exports)
300
+ const exportPattern = /export\s+(?:async\s+)?function\s+(\w+)/g
301
+ while ((match = exportPattern.exec(code)) !== null) {
302
+ const name = match[1]
303
+ if (!endpoints.find((e) => e.name === name)) {
304
+ endpoints.push({ name, params: {} })
305
+ }
306
+ }
307
+
308
+ return endpoints
309
+ }
310
+
311
+ private createInstance(code: string): Record<string, Function> {
312
+ // Create a sandboxed execution context
313
+ const exports: Record<string, any> = {}
314
+
315
+ // Wrap code to capture exports
316
+ const wrappedCode = `
317
+ ${code}
318
+ return { ${this.extractExportedNames(code).join(', ')} }
319
+ `
320
+
321
+ try {
322
+ const factory = new Function(wrappedCode)
323
+ return factory()
324
+ } catch (e: any) {
325
+ console.error('Failed to instantiate service:', e)
326
+ return {}
327
+ }
328
+ }
329
+
330
+ private extractExportedNames(code: string): string[] {
331
+ const names: string[] = []
332
+
333
+ // export function name
334
+ const fnPattern = /export\s+(?:async\s+)?function\s+(\w+)/g
335
+ let match
336
+ while ((match = fnPattern.exec(code)) !== null) {
337
+ names.push(match[1])
338
+ }
339
+
340
+ // export const name
341
+ const constPattern = /export\s+const\s+(\w+)/g
342
+ while ((match = constPattern.exec(code)) !== null) {
343
+ names.push(match[1])
344
+ }
345
+
346
+ return names
347
+ }
348
+ }
349
+
350
+ // ============================================================================
351
+ // Global Instance
352
+ // ============================================================================
353
+
354
+ let globalHost: ServiceHost | null = null
355
+
356
+ export function getServiceHost(): ServiceHost {
357
+ if (!globalHost) {
358
+ globalHost = new ServiceHost()
359
+ // Make available globally for client modules
360
+ ;(globalThis as any).__serviceHost = globalHost
361
+ }
362
+ return globalHost
363
+ }
364
+
365
+ // ============================================================================
366
+ // Convenience Functions
367
+ // ============================================================================
368
+
369
+ /**
370
+ * Deploy a service and return a typed client
371
+ */
372
+ export async function createService<T extends Record<string, Function>>(
373
+ name: string,
374
+ code: string
375
+ ): Promise<T> {
376
+ const host = getServiceHost()
377
+ await host.deploy(name, code)
378
+ return host.createClient<T>(name)
379
+ }
380
+
381
+ /**
382
+ * Get a client for an already-deployed service
383
+ */
384
+ export function getService<T extends Record<string, Function>>(
385
+ name: string
386
+ ): T {
387
+ const host = getServiceHost()
388
+ return host.createClient<T>(name)
389
+ }