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.
- package/CONTEXT.md +594 -0
- package/LICENSE +190 -0
- package/README.md +220 -0
- package/bin/benchmarks.ts +351 -0
- package/bin/dev.ts +205 -0
- package/bin/docs.js +170 -0
- package/bin/install-cursor.sh +71 -0
- package/bin/install-vscode.sh +71 -0
- package/bin/select-local-models.d.ts +1 -0
- package/bin/select-local-models.js +28 -0
- package/bin/select-local-models.ts +31 -0
- package/demo/autocomplete.test.ts +232 -0
- package/demo/docs.json +186 -0
- package/demo/examples.test.ts +598 -0
- package/demo/index.html +91 -0
- package/demo/src/autocomplete.ts +482 -0
- package/demo/src/capabilities.ts +859 -0
- package/demo/src/demo-nav.ts +2097 -0
- package/demo/src/examples.test.ts +161 -0
- package/demo/src/examples.ts +476 -0
- package/demo/src/imports.test.ts +196 -0
- package/demo/src/imports.ts +421 -0
- package/demo/src/index.ts +639 -0
- package/demo/src/module-store.ts +635 -0
- package/demo/src/module-sw.ts +132 -0
- package/demo/src/playground.ts +949 -0
- package/demo/src/service-host.ts +389 -0
- package/demo/src/settings.ts +440 -0
- package/demo/src/style.ts +280 -0
- package/demo/src/tjs-playground.ts +1605 -0
- package/demo/src/ts-examples.ts +478 -0
- package/demo/src/ts-playground.ts +1092 -0
- package/demo/static/favicon.svg +30 -0
- package/demo/static/photo-1.jpg +0 -0
- package/demo/static/photo-2.jpg +0 -0
- package/demo/static/texts/ai-history.txt +9 -0
- package/demo/static/texts/coffee-origins.txt +9 -0
- package/demo/static/texts/renewable-energy.txt +9 -0
- package/dist/index.js +256 -0
- package/dist/index.js.map +37 -0
- package/dist/tjs-batteries.js +4 -0
- package/dist/tjs-batteries.js.map +15 -0
- package/dist/tjs-full.js +256 -0
- package/dist/tjs-full.js.map +37 -0
- package/dist/tjs-transpiler.js +220 -0
- package/dist/tjs-transpiler.js.map +21 -0
- package/dist/tjs-vm.js +4 -0
- package/dist/tjs-vm.js.map +14 -0
- package/docs/CNAME +1 -0
- package/docs/favicon.svg +30 -0
- package/docs/index.html +91 -0
- package/docs/index.js +10468 -0
- package/docs/index.js.map +92 -0
- package/docs/photo-1.jpg +0 -0
- package/docs/photo-1.webp +0 -0
- package/docs/photo-2.jpg +0 -0
- package/docs/photo-2.webp +0 -0
- package/docs/texts/ai-history.txt +9 -0
- package/docs/texts/coffee-origins.txt +9 -0
- package/docs/texts/renewable-energy.txt +9 -0
- package/docs/tjs-lang.svg +31 -0
- package/docs/tosijs-agent.svg +31 -0
- package/editors/README.md +325 -0
- package/editors/ace/ajs-mode.js +328 -0
- package/editors/ace/ajs-mode.ts +269 -0
- package/editors/ajs-syntax.ts +212 -0
- package/editors/build-grammars.ts +510 -0
- package/editors/codemirror/ajs-language.js +287 -0
- package/editors/codemirror/ajs-language.ts +1447 -0
- package/editors/codemirror/autocomplete.test.ts +531 -0
- package/editors/codemirror/component.ts +404 -0
- package/editors/monaco/ajs-monarch.js +243 -0
- package/editors/monaco/ajs-monarch.ts +225 -0
- package/editors/tjs-syntax.ts +115 -0
- package/editors/vscode/language-configuration.json +37 -0
- package/editors/vscode/package.json +65 -0
- package/editors/vscode/syntaxes/ajs-injection.tmLanguage.json +107 -0
- package/editors/vscode/syntaxes/ajs.tmLanguage.json +252 -0
- package/editors/vscode/syntaxes/tjs.tmLanguage.json +333 -0
- package/package.json +83 -0
- package/src/cli/commands/check.ts +41 -0
- package/src/cli/commands/convert.ts +133 -0
- package/src/cli/commands/emit.ts +260 -0
- package/src/cli/commands/run.ts +68 -0
- package/src/cli/commands/test.ts +194 -0
- package/src/cli/commands/types.ts +20 -0
- package/src/cli/create-app.ts +236 -0
- package/src/cli/playground.ts +250 -0
- package/src/cli/tjs.ts +166 -0
- package/src/cli/tjsx.ts +160 -0
- 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
|
+
}
|