@elliots/typical-compiler 0.2.0-beta.1

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,32 @@
1
+ import type { ProjectHandle, TransformResult } from './types.js';
2
+ export interface TypicalCompilerOptions {
3
+ /** Path to the typical binary. If not provided, uses the bundled binary. */
4
+ binaryPath?: string;
5
+ /** Current working directory for the compiler. */
6
+ cwd?: string;
7
+ }
8
+ export declare class TypicalCompiler {
9
+ private process;
10
+ private pendingRequests;
11
+ private buffer;
12
+ private binaryPath;
13
+ private cwd;
14
+ private nextRequestId;
15
+ constructor(options?: TypicalCompilerOptions);
16
+ start(): Promise<void>;
17
+ close(): Promise<void>;
18
+ loadProject(configFileName: string): Promise<ProjectHandle>;
19
+ transformFile(project: ProjectHandle | string, fileName: string): Promise<TransformResult>;
20
+ release(handle: ProjectHandle | string): Promise<void>;
21
+ /**
22
+ * Transform a standalone TypeScript source string.
23
+ * Creates a temporary project to enable type checking.
24
+ *
25
+ * @param fileName - Virtual filename for error messages (e.g., "test.ts")
26
+ * @param source - TypeScript source code
27
+ * @returns Transformed code with validation
28
+ */
29
+ transformSource(fileName: string, source: string): Promise<TransformResult>;
30
+ private request;
31
+ private handleData;
32
+ }
package/dist/client.js ADDED
@@ -0,0 +1,161 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { join, dirname } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { createRequire } from 'node:module';
5
+ import { encodeRequest, decodeResponse } from './protocol.js';
6
+ const __dirname = dirname(fileURLToPath(import.meta.url));
7
+ const require = createRequire(import.meta.url);
8
+ const debug = process.env.DEBUG === '1';
9
+ function debugLog(...args) {
10
+ if (debug) {
11
+ console.error(...args);
12
+ }
13
+ }
14
+ function getBinaryPath() {
15
+ // Try platform-specific package first
16
+ const platform = process.platform; // darwin, linux, win32
17
+ const arch = process.arch; // arm64, x64
18
+ const pkgName = `@elliots/typical-compiler-${platform}-${arch}`;
19
+ try {
20
+ const pkg = require(pkgName);
21
+ debugLog(`[CLIENT] Using platform binary from ${pkgName}`);
22
+ return pkg.binaryPath;
23
+ }
24
+ catch {
25
+ // Fallback to local bin (for development)
26
+ debugLog(`[CLIENT] Platform package ${pkgName} not found, falling back to local bin`);
27
+ return join(__dirname, '..', 'bin', 'typical');
28
+ }
29
+ }
30
+ export class TypicalCompiler {
31
+ process = null;
32
+ pendingRequests = new Map();
33
+ buffer = Buffer.alloc(0);
34
+ binaryPath;
35
+ cwd;
36
+ nextRequestId = 0;
37
+ constructor(options = {}) {
38
+ this.binaryPath = options.binaryPath ?? getBinaryPath();
39
+ this.cwd = options.cwd ?? process.cwd();
40
+ }
41
+ async start() {
42
+ if (this.process) {
43
+ throw new Error('Compiler already started');
44
+ }
45
+ this.process = spawn(this.binaryPath, ['--cwd', this.cwd], {
46
+ stdio: ['pipe', 'pipe', 'inherit'],
47
+ });
48
+ // Don't let the child process keep the Node process alive
49
+ this.process.unref();
50
+ this.process.stdout.on('data', (data) => {
51
+ this.handleData(data);
52
+ });
53
+ this.process.on('error', err => {
54
+ console.error('Compiler process error:', err);
55
+ });
56
+ this.process.on('exit', code => {
57
+ this.process = null;
58
+ // Reject any pending requests
59
+ for (const [, { reject }] of this.pendingRequests) {
60
+ reject(new Error(`Compiler process exited with code ${code}`));
61
+ }
62
+ this.pendingRequests.clear();
63
+ });
64
+ // Test the connection with echo
65
+ const result = await this.request('echo', 'ping');
66
+ if (result !== 'ping') {
67
+ throw new Error(`Echo test failed: expected "ping", got "${result}"`);
68
+ }
69
+ }
70
+ async close() {
71
+ if (this.process) {
72
+ const proc = this.process;
73
+ this.process = null;
74
+ // Clear pending requests to avoid errors after close
75
+ this.pendingRequests.clear();
76
+ proc.stdin?.end();
77
+ proc.kill();
78
+ }
79
+ }
80
+ async loadProject(configFileName) {
81
+ return this.request('loadProject', { configFileName });
82
+ }
83
+ async transformFile(project, fileName) {
84
+ const projectId = typeof project === 'string' ? project : project.id;
85
+ return this.request('transformFile', {
86
+ project: projectId,
87
+ fileName,
88
+ });
89
+ }
90
+ async release(handle) {
91
+ const id = typeof handle === 'string' ? handle : handle.id;
92
+ await this.request('release', id);
93
+ }
94
+ /**
95
+ * Transform a standalone TypeScript source string.
96
+ * Creates a temporary project to enable type checking.
97
+ *
98
+ * @param fileName - Virtual filename for error messages (e.g., "test.ts")
99
+ * @param source - TypeScript source code
100
+ * @returns Transformed code with validation
101
+ */
102
+ async transformSource(fileName, source) {
103
+ return this.request('transformSource', {
104
+ fileName,
105
+ source,
106
+ });
107
+ }
108
+ async request(method, payload) {
109
+ if (!this.process) {
110
+ throw new Error('Compiler not started');
111
+ }
112
+ // Use unique request ID to correlate request/response
113
+ const requestId = `${method}:${this.nextRequestId++}`;
114
+ const requestData = encodeRequest(requestId, payload);
115
+ return new Promise((resolve, reject) => {
116
+ this.pendingRequests.set(requestId, {
117
+ resolve: resolve,
118
+ reject,
119
+ });
120
+ this.process.stdin.write(requestData);
121
+ });
122
+ }
123
+ handleData(data) {
124
+ // Append new data to buffer
125
+ this.buffer = Buffer.concat([this.buffer, data]);
126
+ debugLog(`[CLIENT DEBUG] handleData: received ${data.length} bytes, buffer now ${this.buffer.length} bytes`);
127
+ // Try to decode messages
128
+ while (this.buffer.length > 0) {
129
+ try {
130
+ debugLog(`[CLIENT DEBUG] Attempting to decode ${this.buffer.length} bytes...`);
131
+ const { messageType, method, payload, bytesConsumed } = decodeResponse(this.buffer);
132
+ debugLog(`[CLIENT DEBUG] Decoded: type=${messageType} method=${method} payload=${payload.length} bytes, consumed=${bytesConsumed}`);
133
+ // Find the pending request
134
+ const pending = this.pendingRequests.get(method);
135
+ if (!pending) {
136
+ const pendingKeys = [...this.pendingRequests.keys()].join(', ') || '(none)';
137
+ throw new Error(`No pending request for method: ${method}. Pending requests: ${pendingKeys}. ` + `This indicates a protocol bug - received response for a request that wasn't made or was already resolved.`);
138
+ }
139
+ this.pendingRequests.delete(method);
140
+ if (messageType === 4 /* MessageType.Response */) {
141
+ // Parse JSON payload
142
+ const result = payload.length > 0 ? JSON.parse(payload.toString('utf8')) : null;
143
+ pending.resolve(result);
144
+ }
145
+ else if (messageType === 5 /* MessageType.Error */) {
146
+ pending.reject(new Error(payload.toString('utf8')));
147
+ }
148
+ else {
149
+ pending.reject(new Error(`Unexpected message type: ${messageType}`));
150
+ }
151
+ // Remove only the processed bytes from buffer
152
+ this.buffer = this.buffer.subarray(bytesConsumed);
153
+ }
154
+ catch (e) {
155
+ // Not enough data yet, wait for more
156
+ debugLog(`[CLIENT DEBUG] Decode failed (waiting for more data): ${e}`);
157
+ break;
158
+ }
159
+ }
160
+ }
161
+ }
@@ -0,0 +1,2 @@
1
+ export { TypicalCompiler, type TypicalCompilerOptions } from './client.js';
2
+ export type { ProjectHandle, TransformResult, RawSourceMap } from './types.js';
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ export { TypicalCompiler } from './client.js';
@@ -0,0 +1,21 @@
1
+ /**
2
+ * MessagePack-like binary protocol for communicating with the typical compiler.
3
+ * This matches the protocol used by tsgo's --api mode.
4
+ */
5
+ export declare const enum MessageType {
6
+ Unknown = 0,
7
+ Request = 1,
8
+ CallResponse = 2,
9
+ CallError = 3,
10
+ Response = 4,
11
+ Error = 5,
12
+ Call = 6
13
+ }
14
+ export declare function encodeRequest(method: string, payload: unknown): Buffer;
15
+ export interface DecodedMessage {
16
+ messageType: MessageType;
17
+ method: string;
18
+ payload: Buffer;
19
+ bytesConsumed: number;
20
+ }
21
+ export declare function decodeResponse(data: Buffer): DecodedMessage;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * MessagePack-like binary protocol for communicating with the typical compiler.
3
+ * This matches the protocol used by tsgo's --api mode.
4
+ */
5
+ const MessagePackTypeFixedArray3 = 0x93;
6
+ const MessagePackTypeBin8 = 0xc4;
7
+ const MessagePackTypeBin16 = 0xc5;
8
+ const MessagePackTypeBin32 = 0xc6;
9
+ const MessagePackTypeU8 = 0xcc;
10
+ export function encodeRequest(method, payload) {
11
+ const methodBuf = Buffer.from(method, 'utf8');
12
+ const payloadStr = JSON.stringify(payload);
13
+ const payloadBuf = Buffer.from(payloadStr, 'utf8');
14
+ // Calculate total size
15
+ // 1 byte for array marker (0x93)
16
+ // 2 bytes for message type (0xCC + type)
17
+ // variable for method bin
18
+ // variable for payload bin
19
+ const methodBinSize = getBinEncodingSize(methodBuf.length);
20
+ const payloadBinSize = getBinEncodingSize(payloadBuf.length);
21
+ const totalSize = 1 + 2 + methodBinSize + methodBuf.length + payloadBinSize + payloadBuf.length;
22
+ const buf = Buffer.alloc(totalSize);
23
+ let offset = 0;
24
+ // Fixed array of 3 elements
25
+ buf[offset++] = MessagePackTypeFixedArray3;
26
+ // Message type (u8)
27
+ buf[offset++] = MessagePackTypeU8;
28
+ buf[offset++] = 1 /* MessageType.Request */;
29
+ // Method (bin)
30
+ offset = writeBin(buf, offset, methodBuf);
31
+ // Payload (bin)
32
+ offset = writeBin(buf, offset, payloadBuf);
33
+ return buf;
34
+ }
35
+ export function decodeResponse(data) {
36
+ let offset = 0;
37
+ // Check array marker
38
+ if (data[offset++] !== MessagePackTypeFixedArray3) {
39
+ throw new Error(`Expected 0x93, got 0x${data[0].toString(16)}`);
40
+ }
41
+ // Read message type
42
+ if (data[offset++] !== MessagePackTypeU8) {
43
+ throw new Error(`Expected 0xCC for message type`);
44
+ }
45
+ const messageType = data[offset++];
46
+ // Read method
47
+ const { value: methodBuf, newOffset: offset2 } = readBin(data, offset);
48
+ offset = offset2;
49
+ const method = methodBuf.toString('utf8');
50
+ // Read payload
51
+ const { value: payload, newOffset: offset3 } = readBin(data, offset);
52
+ return { messageType, method, payload, bytesConsumed: offset3 };
53
+ }
54
+ function getBinEncodingSize(length) {
55
+ if (length < 256)
56
+ return 2; // 1 byte type + 1 byte length
57
+ if (length < 65536)
58
+ return 3; // 1 byte type + 2 bytes length
59
+ return 5; // 1 byte type + 4 bytes length
60
+ }
61
+ function writeBin(buf, offset, data) {
62
+ const length = data.length;
63
+ if (length < 256) {
64
+ buf[offset++] = MessagePackTypeBin8;
65
+ buf[offset++] = length;
66
+ }
67
+ else if (length < 65536) {
68
+ buf[offset++] = MessagePackTypeBin16;
69
+ buf.writeUInt16BE(length, offset);
70
+ offset += 2;
71
+ }
72
+ else {
73
+ buf[offset++] = MessagePackTypeBin32;
74
+ buf.writeUInt32BE(length, offset);
75
+ offset += 4;
76
+ }
77
+ data.copy(buf, offset);
78
+ return offset + length;
79
+ }
80
+ function readBin(buf, offset) {
81
+ if (offset >= buf.length) {
82
+ throw new Error('Not enough data: need type byte');
83
+ }
84
+ const type = buf[offset++];
85
+ let length;
86
+ switch (type) {
87
+ case MessagePackTypeBin8:
88
+ if (offset >= buf.length)
89
+ throw new Error('Not enough data: need length byte');
90
+ length = buf[offset++];
91
+ break;
92
+ case MessagePackTypeBin16:
93
+ if (offset + 2 > buf.length)
94
+ throw new Error('Not enough data: need 2 length bytes');
95
+ length = buf.readUInt16BE(offset);
96
+ offset += 2;
97
+ break;
98
+ case MessagePackTypeBin32:
99
+ if (offset + 4 > buf.length)
100
+ throw new Error('Not enough data: need 4 length bytes');
101
+ length = buf.readUInt32BE(offset);
102
+ offset += 4;
103
+ break;
104
+ default:
105
+ throw new Error(`Expected bin type (0xC4-0xC6), got 0x${type.toString(16)}`);
106
+ }
107
+ // Check if we have enough data for the full payload
108
+ if (offset + length > buf.length) {
109
+ throw new Error(`Not enough data: need ${length} bytes, have ${buf.length - offset}`);
110
+ }
111
+ const value = buf.subarray(offset, offset + length);
112
+ return { value, newOffset: offset + length };
113
+ }
@@ -0,0 +1,18 @@
1
+ export interface ProjectHandle {
2
+ id: string;
3
+ configFile: string;
4
+ rootFiles: string[];
5
+ }
6
+ export interface RawSourceMap {
7
+ version: number;
8
+ file: string;
9
+ sourceRoot?: string;
10
+ sources: string[];
11
+ names: string[];
12
+ mappings: string;
13
+ sourcesContent?: (string | null)[];
14
+ }
15
+ export interface TransformResult {
16
+ code: string;
17
+ sourceMap?: RawSourceMap;
18
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@elliots/typical-compiler",
3
+ "version": "0.2.0-beta.1",
4
+ "description": "TypeScript compiler powered by tsgo for typical validation",
5
+ "keywords": [
6
+ "compiler",
7
+ "runtime",
8
+ "typescript",
9
+ "validation"
10
+ ],
11
+ "license": "MIT",
12
+ "author": "elliots",
13
+ "files": [
14
+ "dist"
15
+ ],
16
+ "type": "module",
17
+ "main": "dist/index.js",
18
+ "types": "dist/index.d.ts",
19
+ "exports": {
20
+ ".": {
21
+ "types": "./dist/index.d.ts",
22
+ "import": "./dist/index.js"
23
+ }
24
+ },
25
+ "devDependencies": {
26
+ "typescript": "^5.7.0"
27
+ },
28
+ "optionalDependencies": {
29
+ "@elliots/typical-compiler-darwin-arm64": "0.2.0-beta.1",
30
+ "@elliots/typical-compiler-darwin-x64": "0.2.0-beta.1",
31
+ "@elliots/typical-compiler-linux-arm64": "0.2.0-beta.1",
32
+ "@elliots/typical-compiler-linux-x64": "0.2.0-beta.1",
33
+ "@elliots/typical-compiler-win32-arm64": "0.2.0-beta.1",
34
+ "@elliots/typical-compiler-win32-x64": "0.2.0-beta.1"
35
+ },
36
+ "scripts": {
37
+ "build": "tsc",
38
+ "build:go": "./go/scripts/sync-shims.sh && cd go && go build -o ../bin/typical ./cmd/typical",
39
+ "test": "npx tsx test/e2e.ts"
40
+ }
41
+ }