@mt-tl/tl 0.1.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 (64) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +40 -0
  3. package/dist/cli.d.ts +3 -0
  4. package/dist/cli.d.ts.map +1 -0
  5. package/dist/cli.js +47 -0
  6. package/dist/cli.js.map +1 -0
  7. package/dist/codegen/gen-types.d.ts +10 -0
  8. package/dist/codegen/gen-types.d.ts.map +1 -0
  9. package/dist/codegen/gen-types.js +190 -0
  10. package/dist/codegen/gen-types.js.map +1 -0
  11. package/dist/index.d.ts +13 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +21 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/logger.d.ts +63 -0
  16. package/dist/logger.d.ts.map +1 -0
  17. package/dist/logger.js +147 -0
  18. package/dist/logger.js.map +1 -0
  19. package/dist/migrate.d.ts +37 -0
  20. package/dist/migrate.d.ts.map +1 -0
  21. package/dist/migrate.js +84 -0
  22. package/dist/migrate.js.map +1 -0
  23. package/dist/rpc.d.ts +74 -0
  24. package/dist/rpc.d.ts.map +1 -0
  25. package/dist/rpc.js +2 -0
  26. package/dist/rpc.js.map +1 -0
  27. package/dist/tl/crc32.d.ts +2 -0
  28. package/dist/tl/crc32.d.ts.map +1 -0
  29. package/dist/tl/crc32.js +42 -0
  30. package/dist/tl/crc32.js.map +1 -0
  31. package/dist/tl/ir.d.ts +68 -0
  32. package/dist/tl/ir.d.ts.map +1 -0
  33. package/dist/tl/ir.js +63 -0
  34. package/dist/tl/ir.js.map +1 -0
  35. package/dist/tl/parser.d.ts +22 -0
  36. package/dist/tl/parser.d.ts.map +1 -0
  37. package/dist/tl/parser.js +122 -0
  38. package/dist/tl/parser.js.map +1 -0
  39. package/dist/tl/value.d.ts +26 -0
  40. package/dist/tl/value.d.ts.map +1 -0
  41. package/dist/tl/value.js +44 -0
  42. package/dist/tl/value.js.map +1 -0
  43. package/dist/tools/freeze-layer.d.ts +26 -0
  44. package/dist/tools/freeze-layer.d.ts.map +1 -0
  45. package/dist/tools/freeze-layer.js +86 -0
  46. package/dist/tools/freeze-layer.js.map +1 -0
  47. package/dist/wire.d.ts +12 -0
  48. package/dist/wire.d.ts.map +1 -0
  49. package/dist/wire.js +50 -0
  50. package/dist/wire.js.map +1 -0
  51. package/package.json +76 -0
  52. package/schema/scheme_0_protocol.tl +150 -0
  53. package/src/cli.ts +48 -0
  54. package/src/codegen/gen-types.ts +203 -0
  55. package/src/index.ts +22 -0
  56. package/src/logger.ts +210 -0
  57. package/src/migrate.ts +101 -0
  58. package/src/rpc.ts +70 -0
  59. package/src/tl/crc32.ts +44 -0
  60. package/src/tl/ir.ts +105 -0
  61. package/src/tl/parser.ts +144 -0
  62. package/src/tl/value.ts +60 -0
  63. package/src/tools/freeze-layer.ts +108 -0
  64. package/src/wire.ts +54 -0
@@ -0,0 +1 @@
1
+ {"version":3,"file":"freeze-layer.d.ts","sourceRoot":"","sources":["../../src/tools/freeze-layer.ts"],"names":[],"mappings":"AAaA,MAAM,WAAW,YAAY;IACzB,YAAY,EAAE,MAAM,CAAA;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,mDAAmD;IACnD,GAAG,EAAE,MAAM,CAAA;IACX,kFAAkF;IAClF,KAAK,EAAE,MAAM,CAAA;IACb,WAAW,EAAE,MAAM,CAAA;CACtB;AAiBD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,WAAW,CAAC,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,YAAY,CAqC1F"}
@@ -0,0 +1,86 @@
1
+ import { mkdirSync, writeFileSync } from 'node:fs';
2
+ import { join } from 'node:path';
3
+ import { pathToFileURL } from 'node:url';
4
+ import { parseSchemaDir } from '../tl/parser.js';
5
+ /** A single `name#id p:type … = ResultType;` line. */
6
+ function tlLine(e) {
7
+ const name = e.predicate ?? e.method ?? '?';
8
+ const params = e.params.map(p => `${p.name}:${p.type}`).join(' ');
9
+ return `${name}#${e.id}${params ? ' ' + params : ''} = ${e.type};`;
10
+ }
11
+ /** Reconstruct a readable `.tl` from the frozen IR (constructors, then methods). */
12
+ function toTlText(constructors, methods, layer) {
13
+ const lines = [`// Frozen snapshot of layer ${layer}. Generated by \`freeze\` — do not edit.`, ''];
14
+ lines.push(...constructors.map(tlLine));
15
+ if (methods.length)
16
+ lines.push('', '---functions---', '', ...methods.map(tlLine));
17
+ return lines.join('\n') + '\n';
18
+ }
19
+ /**
20
+ * Freezes the `.tl` schema in `schemaDir` into a per-layer snapshot
21
+ * `<outDir>/scheme_<layer>.json` (loaded by the gateway) plus a human-readable
22
+ * `<outDir>/scheme_<layer>.tl` mirror (for inspection/diffs; not loaded). Run
23
+ * this when you SHIP a layer: the `.tl` you edit is the in-progress newest layer;
24
+ * the frozen snapshot is the historical record the gateway uses to encode for
25
+ * clients on that layer.
26
+ *
27
+ * The snapshot is BUSINESS-only: the immutable MTProto protocol/core types are
28
+ * excluded (they live in @mt-tl/tl and are loaded globally), so a business
29
+ * layer never carries protocol definitions.
30
+ *
31
+ * Schema ownership lives in the consumer app, so paths are explicit — apps wrap
32
+ * this in their own `freeze` script.
33
+ */
34
+ export function freezeLayer(schemaDir, outDir, layer) {
35
+ const { defs, crcMismatches } = parseSchemaDir(schemaDir);
36
+ const constructors = [];
37
+ const methods = [];
38
+ const seen = new Set();
39
+ for (const d of defs) {
40
+ // Per-layer snapshots are BUSINESS-only. The MTProto protocol layer
41
+ // (scheme_0_protocol + the manually-parsed core ctors `parseSchemaDir`
42
+ // injects) is immutable and layer-independent — it lives in @mt-tl/tl
43
+ // and the gateway always loads it globally, so it must NOT be baked into
44
+ // each business snapshot (mirrors Telegram's split: mtproto_api vs api).
45
+ if (d.isProtocol)
46
+ continue;
47
+ if (seen.has(d.id))
48
+ continue;
49
+ seen.add(d.id);
50
+ const params = d.params.map(p => ({ name: p.name, type: p.raw }));
51
+ if (d.kind === 'method')
52
+ methods.push({ id: d.id, method: d.name, params, type: d.type });
53
+ else
54
+ constructors.push({ id: d.id, predicate: d.name, params, type: d.type });
55
+ }
56
+ const byId = (a, b) => parseInt(a.id, 16) - parseInt(b.id, 16);
57
+ constructors.sort(byId);
58
+ methods.sort(byId);
59
+ mkdirSync(outDir, { recursive: true });
60
+ const out = join(outDir, `scheme_${layer}.json`);
61
+ const tlOut = join(outDir, `scheme_${layer}.tl`);
62
+ writeFileSync(out, JSON.stringify({ constructors, methods }, null, 2));
63
+ writeFileSync(tlOut, toTlText(constructors, methods, layer));
64
+ return {
65
+ constructors: constructors.length,
66
+ methods: methods.length,
67
+ out,
68
+ tlOut,
69
+ crcWarnings: crcMismatches.length,
70
+ };
71
+ }
72
+ // CLI: `tsx freeze-layer.ts <layer> <schemaDir> <outDir>`
73
+ // or env: `SCHEMA_DIR=… SCHEMA_LAYERS_DIR=… tsx freeze-layer.ts <layer>`
74
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
75
+ const layer = Number(process.argv[2]);
76
+ const schemaDir = process.argv[3] || process.env.SCHEMA_DIR;
77
+ const outDir = process.argv[4] || process.env.SCHEMA_LAYERS_DIR;
78
+ if (!Number.isInteger(layer) || layer <= 0 || !schemaDir || !outDir) {
79
+ console.error('usage: freeze-layer <layer> <schemaDir> <outDir>');
80
+ process.exit(1);
81
+ }
82
+ const r = freezeLayer(schemaDir, outDir, layer);
83
+ console.log(`Froze layer ${layer}: ${r.constructors} constructors, ${r.methods} methods -> ${r.out} (+ ${r.tlOut})` +
84
+ (r.crcWarnings ? ` (${r.crcWarnings} crc warnings)` : ''));
85
+ }
86
+ //# sourceMappingURL=freeze-layer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"freeze-layer.js","sourceRoot":"","sources":["../../src/tools/freeze-layer.ts"],"names":[],"mappings":"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"}
package/dist/wire.d.ts ADDED
@@ -0,0 +1,12 @@
1
+ import type { RpcRequest, RpcResponse } from './rpc.js';
2
+ /**
3
+ * Wire format for the gateway↔worker RPC, shared so both sides agree:
4
+ * request { jsonrpc, id, method, params, context }
5
+ * response { result? , error?: { code, message }, effects? }
6
+ * Gateway encodes requests / decodes responses; workers do the inverse.
7
+ */
8
+ export declare function encodeRequest(req: RpcRequest): unknown;
9
+ export declare function decodeRequest(json: unknown): RpcRequest | null;
10
+ export declare function encodeResponse(res: RpcResponse): unknown;
11
+ export declare function decodeResponse(json: unknown): RpcResponse;
12
+ //# sourceMappingURL=wire.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wire.d.ts","sourceRoot":"","sources":["../src/wire.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAc,UAAU,EAAE,WAAW,EAAiB,MAAM,UAAU,CAAA;AAElF;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,UAAU,GAAG,OAAO,CAQtD;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,OAAO,GAAG,UAAU,GAAG,IAAI,CAU9D;AAED,wBAAgB,cAAc,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAExD;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,OAAO,GAAG,WAAW,CAkBzD"}
package/dist/wire.js ADDED
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Wire format for the gateway↔worker RPC, shared so both sides agree:
3
+ * request { jsonrpc, id, method, params, context }
4
+ * response { result? , error?: { code, message }, effects? }
5
+ * Gateway encodes requests / decodes responses; workers do the inverse.
6
+ */
7
+ export function encodeRequest(req) {
8
+ return {
9
+ jsonrpc: '2.0',
10
+ id: req.id,
11
+ method: req.method,
12
+ params: req.params,
13
+ context: req.context,
14
+ };
15
+ }
16
+ export function decodeRequest(json) {
17
+ if (!json || typeof json !== 'object')
18
+ return null;
19
+ const o = json;
20
+ if (typeof o.method !== 'string' || !o.context || typeof o.context !== 'object')
21
+ return null;
22
+ return {
23
+ id: o.id === undefined ? '' : String(o.id),
24
+ method: o.method,
25
+ params: (o.params ?? {}),
26
+ context: o.context,
27
+ };
28
+ }
29
+ export function encodeResponse(res) {
30
+ return res;
31
+ }
32
+ export function decodeResponse(json) {
33
+ if (!json || typeof json !== 'object') {
34
+ return { error: { code: 502, message: 'BAD_RESPONSE' } };
35
+ }
36
+ const o = json;
37
+ const effects = Array.isArray(o.effects) ? o.effects : undefined;
38
+ if (o.error && typeof o.error === 'object') {
39
+ const e = o.error;
40
+ return {
41
+ error: {
42
+ code: typeof e.code === 'number' ? e.code : 500,
43
+ message: typeof e.message === 'string' ? e.message : 'ERROR',
44
+ },
45
+ effects,
46
+ };
47
+ }
48
+ return { result: o.result, effects };
49
+ }
50
+ //# sourceMappingURL=wire.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wire.js","sourceRoot":"","sources":["../src/wire.ts"],"names":[],"mappings":"AAGA;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,GAAe;IACzC,OAAO;QACH,OAAO,EAAE,KAAK;QACd,EAAE,EAAE,GAAG,CAAC,EAAE;QACV,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,MAAM,EAAE,GAAG,CAAC,MAAM;QAClB,OAAO,EAAE,GAAG,CAAC,OAAO;KACvB,CAAA;AACL,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,IAAa;IACvC,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IAClD,MAAM,CAAC,GAAG,IAA+B,CAAA;IACzC,IAAI,OAAO,CAAC,CAAC,MAAM,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ;QAAE,OAAO,IAAI,CAAA;IAC5F,OAAO;QACH,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC;QAC1C,MAAM,EAAE,CAAC,CAAC,MAAM;QAChB,MAAM,EAAE,CAAC,CAAC,CAAC,MAAM,IAAI,EAAE,CAAc;QACrC,OAAO,EAAE,CAAC,CAAC,OAAqB;KACnC,CAAA;AACL,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,GAAgB;IAC3C,OAAO,GAAG,CAAA;AACd,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,IAAa;IACxC,IAAI,CAAC,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;QACpC,OAAO,EAAE,KAAK,EAAE,EAAE,IAAI,EAAE,GAAG,EAAE,OAAO,EAAE,cAAc,EAAE,EAAE,CAAA;IAC5D,CAAC;IACD,MAAM,CAAC,GAAG,IAA+B,CAAA;IACzC,MAAM,OAAO,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAE,CAAC,CAAC,OAA2B,CAAC,CAAC,CAAC,SAAS,CAAA;IAErF,IAAI,CAAC,CAAC,KAAK,IAAI,OAAO,CAAC,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QACzC,MAAM,CAAC,GAAG,CAAC,CAAC,KAAgC,CAAA;QAC5C,OAAO;YACH,KAAK,EAAE;gBACH,IAAI,EAAE,OAAO,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG;gBAC/C,OAAO,EAAE,OAAO,CAAC,CAAC,OAAO,KAAK,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO;aAC/D;YACD,OAAO;SACV,CAAA;IACL,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC,MAAmB,EAAE,OAAO,EAAE,CAAA;AACrD,CAAC"}
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@mt-tl/tl",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "description": "TL (Type Language) tooling for MTProto: parser/IR, generic wire codec, value↔JSON, migrations, a TypeScript type generator, layer freezing, and the bundled MTProto protocol schema.",
6
+ "license": "MIT",
7
+ "author": "Joe Beretta",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/joeberetta/mt-tl.git",
11
+ "directory": "packages/tl"
12
+ },
13
+ "homepage": "https://github.com/joeberetta/mt-tl/tree/master/packages/tl#readme",
14
+ "bugs": "https://github.com/joeberetta/mt-tl/issues",
15
+ "keywords": [
16
+ "mtproto",
17
+ "telegram",
18
+ "tl",
19
+ "type-language",
20
+ "codec",
21
+ "schema",
22
+ "codegen"
23
+ ],
24
+ "engines": {
25
+ "node": ">=20"
26
+ },
27
+ "sideEffects": false,
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.js"
32
+ }
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "src",
37
+ "schema"
38
+ ],
39
+ "scripts": {
40
+ "build": "rm -rf dist && tsc -p tsconfig.build.json",
41
+ "clean": "rm -rf dist",
42
+ "prepack": "yarn run build",
43
+ "typecheck": "tsc --noEmit",
44
+ "test": "vitest run --passWithNoTests"
45
+ },
46
+ "publishConfig": {
47
+ "access": "public",
48
+ "exports": {
49
+ ".": {
50
+ "types": "./dist/index.d.ts",
51
+ "import": "./dist/index.js"
52
+ }
53
+ },
54
+ "main": "./dist/index.js",
55
+ "module": "./dist/index.js",
56
+ "types": "./dist/index.d.ts",
57
+ "bin": {
58
+ "mtproto-tl": "./dist/cli.js"
59
+ }
60
+ },
61
+ "dependencies": {
62
+ "crc-32": "^1.2.2"
63
+ },
64
+ "devDependencies": {
65
+ "@types/node": "^20.14.0",
66
+ "tsx": "^4.19.2",
67
+ "typescript": "^5.6.3",
68
+ "vitest": "^2.1.8"
69
+ },
70
+ "main": "./dist/index.js",
71
+ "module": "./dist/index.js",
72
+ "bin": {
73
+ "mtproto-tl": "./dist/cli.js"
74
+ },
75
+ "types": "./dist/index.d.ts"
76
+ }
@@ -0,0 +1,150 @@
1
+ // Core types (no need to gen)
2
+
3
+ //vector#1cb5c415 {t:Type} # [ t ] = Vector t;
4
+
5
+ ///////////////////////////////
6
+ /////////////////// Layer cons
7
+ ///////////////////////////////
8
+
9
+ //invokeAfterMsg#cb9f372d msg_id:long query:!X = X;
10
+ //invokeAfterMsgs#3dc4b4f0 msg_ids:Vector<long> query:!X = X;
11
+ //invokeWithLayer1#53835315 query:!X = X;
12
+ //invokeWithLayer2#289dd1f6 query:!X = X;
13
+ //invokeWithLayer3#b7475268 query:!X = X;
14
+ //invokeWithLayer4#dea0d430 query:!X = X;
15
+ //invokeWithLayer5#417a57ae query:!X = X;
16
+ //invokeWithLayer6#3a64d54d query:!X = X;
17
+ //invokeWithLayer7#a5be56d3 query:!X = X;
18
+ //invokeWithLayer8#e9abd9fd query:!X = X;
19
+ //invokeWithLayer9#76715a63 query:!X = X;
20
+ //invokeWithLayer10#39620c41 query:!X = X;
21
+ //invokeWithLayer11#a6b88fdf query:!X = X;
22
+ //invokeWithLayer12#dda60d3c query:!X = X;
23
+ //invokeWithLayer13#427c8ea2 query:!X = X;
24
+ //invokeWithLayer14#2b9b08fa query:!X = X;
25
+ //invokeWithLayer15#b4418b64 query:!X = X;
26
+ //invokeWithLayer16#cf5f0987 query:!X = X;
27
+ //invokeWithLayer17#50858a19 query:!X = X;
28
+ //invokeWithLayer18#1c900537 query:!X = X;
29
+ //invokeWithLayer#da9b0d0d layer:int query:!X = X; // after 18 layer
30
+
31
+ ///////////////////////////////
32
+ /// Authorization key creation
33
+ ///////////////////////////////
34
+
35
+ resPQ#05162463 nonce:int128 server_nonce:int128 pq:string server_public_key_fingerprints:Vector<long> = ResPQ;
36
+
37
+ p_q_inner_data#83c95aec pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 = P_Q_inner_data;
38
+ p_q_inner_data_dc#a9f55f95 pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 dc:int = P_Q_inner_data;
39
+ p_q_inner_data_temp#3c6a84d4 pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 expires_in:int = P_Q_inner_data;
40
+ p_q_inner_data_temp_dc#56fddf88 pq:string p:string q:string nonce:int128 server_nonce:int128 new_nonce:int256 dc:int expires_in:int = P_Q_inner_data;
41
+
42
+ server_DH_params_fail#79cb045d nonce:int128 server_nonce:int128 new_nonce_hash:int128 = Server_DH_Params;
43
+ server_DH_params_ok#d0e8075c nonce:int128 server_nonce:int128 encrypted_answer:string = Server_DH_Params;
44
+
45
+ server_DH_inner_data#b5890dba nonce:int128 server_nonce:int128 g:int dh_prime:string g_a:string server_time:int = Server_DH_inner_data;
46
+
47
+ client_DH_inner_data#6643b654 nonce:int128 server_nonce:int128 retry_id:long g_b:string = Client_DH_Inner_Data;
48
+
49
+ dh_gen_ok#3bcbf734 nonce:int128 server_nonce:int128 new_nonce_hash1:int128 = Set_client_DH_params_answer;
50
+ dh_gen_retry#46dc1fb9 nonce:int128 server_nonce:int128 new_nonce_hash2:int128 = Set_client_DH_params_answer;
51
+ dh_gen_fail#a69dae02 nonce:int128 server_nonce:int128 new_nonce_hash3:int128 = Set_client_DH_params_answer;
52
+
53
+ destroy_auth_key_ok#f660e1d4 = DestroyAuthKeyRes;
54
+ destroy_auth_key_none#0a9f2259 = DestroyAuthKeyRes;
55
+ destroy_auth_key_fail#ea109b13 = DestroyAuthKeyRes;
56
+
57
+ ---functions---
58
+
59
+ req_pq#60469778 nonce:int128 = ResPQ;
60
+ req_pq_multi#be7e8ef1 nonce:int128 = ResPQ;
61
+
62
+ req_DH_params#d712e4be nonce:int128 server_nonce:int128 p:string q:string public_key_fingerprint:long encrypted_data:string = Server_DH_Params;
63
+
64
+ set_client_DH_params#f5045f1f nonce:int128 server_nonce:int128 encrypted_data:string = Set_client_DH_params_answer;
65
+
66
+ destroy_auth_key#d1435160 = DestroyAuthKeyRes;
67
+
68
+ ///////////////////////////////
69
+ ////////////// System messages
70
+ ///////////////////////////////
71
+
72
+ ---types---
73
+
74
+ msgs_ack#62d6b459 msg_ids:Vector<long> = MsgsAck;
75
+
76
+ bad_msg_notification#a7eff811 bad_msg_id:long bad_msg_seqno:int error_code:int = BadMsgNotification;
77
+ bad_server_salt#edab447b bad_msg_id:long bad_msg_seqno:int error_code:int new_server_salt:long = BadMsgNotification;
78
+
79
+ msgs_state_req#da69fb52 msg_ids:Vector<long> = MsgsStateReq;
80
+ msgs_state_info#04deb57d req_msg_id:long info:string = MsgsStateInfo;
81
+ msgs_all_info#8cc0d131 msg_ids:Vector<long> info:string = MsgsAllInfo;
82
+
83
+ msg_detailed_info#276d3ec6 msg_id:long answer_msg_id:long bytes:int status:int = MsgDetailedInfo;
84
+ msg_new_detailed_info#809db6df answer_msg_id:long bytes:int status:int = MsgDetailedInfo;
85
+
86
+ msg_resend_req#7d861a08 msg_ids:Vector<long> = MsgResendReq;
87
+
88
+ //rpc_result#f35c6d01 req_msg_id:long result:Object = RpcResult; // parsed manually
89
+
90
+ rpc_error#2144ca19 error_code:int error_message:string = RpcError;
91
+
92
+ rpc_answer_unknown#5e2ad36e = RpcDropAnswer;
93
+ rpc_answer_dropped_running#cd78e586 = RpcDropAnswer;
94
+ rpc_answer_dropped#a43ad8b7 msg_id:long seq_no:int bytes:int = RpcDropAnswer;
95
+
96
+ future_salt#0949d9dc valid_since:int valid_until:int salt:long = FutureSalt;
97
+ future_salts#ae500895 req_msg_id:long now:int salts:vector<future_salt> = FutureSalts;
98
+
99
+ pong#347773c5 msg_id:long ping_id:long = Pong;
100
+
101
+ destroy_session_ok#e22045fc session_id:long = DestroySessionRes;
102
+ destroy_session_none#62d350c9 session_id:long = DestroySessionRes;
103
+
104
+ new_session_created#9ec20908 first_msg_id:long unique_id:long server_salt:long = NewSession;
105
+
106
+ //message msg_id:long seqno:int bytes:int body:Object = Message; // parsed manually
107
+ //msg_container#73f1f8dc messages:vector<message> = MessageContainer; // parsed manually
108
+ //msg_copy#e06046b2 orig_message:Message = MessageCopy; // parsed manually, not used - use msg_container
109
+ //gzip_packed#3072cfa1 packed_data:string = Object; // parsed manually
110
+
111
+ http_wait#9299359f max_delay:int wait_after:int max_wait:int = HttpWait;
112
+
113
+ //ipPort ipv4:int port:int = IpPort;
114
+ //help.configSimple#d997c3c5 date:int expires:int dc_id:int ip_port_list:Vector<ipPort> = help.ConfigSimple;
115
+
116
+ ipPort#d433ad73 ipv4:int port:int = IpPort;
117
+ ipPortSecret#37982646 ipv4:int port:int secret:bytes = IpPort;
118
+ accessPointRule#4679b65f phone_prefix_rules:string dc_id:int ips:vector<IpPort> = AccessPointRule;
119
+ help.configSimple#5a592a6c date:int expires:int rules:vector<AccessPointRule> = help.ConfigSimple;
120
+
121
+ ---functions---
122
+
123
+ rpc_drop_answer#58e4a740 req_msg_id:long = RpcDropAnswer;
124
+
125
+ get_future_salts#b921bd04 num:int = FutureSalts;
126
+
127
+ ping#7abe77ec ping_id:long = Pong;
128
+ ping_delay_disconnect#f3427b8c ping_id:long disconnect_delay:int = Pong;
129
+
130
+ destroy_session#e7512126 session_id:long = DestroySessionRes;
131
+
132
+ contest.saveDeveloperInfo#9a5f6e95 vk_id:int name:string phone_number:string age:int city:string = Bool;
133
+
134
+ ---types---
135
+
136
+ vector#1cb5c415 {t:Type} # [ t ] = Vector t;
137
+
138
+ ---functions---
139
+
140
+ initConnection#7d7f18cf {X:Type} flags:# api_id:int api_hash:flags.1?string device_model:string system_version:string app_version:string system_lang_code:string lang_pack:string lang_code:string proxy:flags.0?InputClientProxy query:!X = X;
141
+ invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
142
+ invokeAfterMsgs#3dc4b4f0 {X:Type} msg_ids:Vector<long> query:!X = X;
143
+ invokeWithLayer#da9b0d0d {X:Type} layer:int query:!X = X;
144
+ invokeWithoutUpdates#bf9459b7 {X:Type} query:!X = X;
145
+ invokeWithMessagesRange#365275f2 {X:Type} range:MessageRange query:!X = X;
146
+ invokeWithTakeout#aca9fd2e {X:Type} takeout_id:long query:!X = X;
147
+
148
+ ///////////////////////////////
149
+ ///////// Main application API
150
+ ///////////////////////////////
package/src/cli.ts ADDED
@@ -0,0 +1,48 @@
1
+ #!/usr/bin/env node
2
+ import { writeSchemaTs } from './codegen/gen-types.js'
3
+ import { freezeLayer } from './tools/freeze-layer.js'
4
+
5
+ const USAGE = `mtproto-tl — TL tooling
6
+
7
+ Usage:
8
+ mtproto-tl gen-types <schemaDir> <outFile>
9
+ Generate TypeScript types (RpcMethods + interfaces) from a .tl schema.
10
+
11
+ mtproto-tl freeze <schemaDir> <outDir> <layer>
12
+ Freeze the current schema into a per-layer snapshot (scheme_<layer>.json + .tl).
13
+ `
14
+
15
+ function fail(msg: string): never {
16
+ console.error(msg + '\n\n' + USAGE)
17
+ process.exit(1)
18
+ }
19
+
20
+ const [cmd, ...args] = process.argv.slice(2)
21
+
22
+ switch (cmd) {
23
+ case 'gen-types': {
24
+ const [schemaDir, outFile] = args
25
+ if (!schemaDir || !outFile) fail('gen-types requires <schemaDir> <outFile>')
26
+ writeSchemaTs(schemaDir, outFile)
27
+ break
28
+ }
29
+ case 'freeze': {
30
+ const [schemaDir, outDir, layerStr] = args
31
+ if (!schemaDir || !outDir || !layerStr) fail('freeze requires <schemaDir> <outDir> <layer>')
32
+ const layer = Number(layerStr)
33
+ if (!Number.isInteger(layer)) fail(`freeze: <layer> must be an integer, got "${layerStr}"`)
34
+ const res = freezeLayer(schemaDir, outDir, layer)
35
+ console.log(
36
+ `Froze layer ${layer}: ${res.constructors} constructors, ${res.methods} methods → ${res.out}`,
37
+ )
38
+ if (res.crcWarnings) console.warn(` (${res.crcWarnings} CRC warnings)`)
39
+ break
40
+ }
41
+ case undefined:
42
+ case '-h':
43
+ case '--help':
44
+ console.log(USAGE)
45
+ break
46
+ default:
47
+ fail(`unknown command: ${cmd}`)
48
+ }
@@ -0,0 +1,203 @@
1
+ import { mkdirSync, writeFileSync } from 'node:fs'
2
+ import { dirname } from 'node:path'
3
+ import { pathToFileURL } from 'node:url'
4
+ import { parseSchemaDir } from '../tl/parser.js'
5
+ import { protocolSchemaDir as bundledSchemaDir } from '../index.js'
6
+ import type { TlDef, TlType } from '../tl/ir.js'
7
+
8
+ /**
9
+ * Generates `generated/schema.ts` — TypeScript types for the **newest** layer:
10
+ * one interface per constructor, a union alias per TL type, and an `RpcMethods`
11
+ * map (method name → { params; result }) for typed worker handlers.
12
+ *
13
+ * Types only (no runtime). Field shapes are the post-`fromJson` values workers
14
+ * receive: long→bigint, bytes/int128/int256→Buffer, Vector<T>→T[], flags.N?T→
15
+ * optional. Run `yarn gen:types` after editing the schema.
16
+ *
17
+ * yarn workspace @mt-tl/tl run gen:types
18
+ */
19
+
20
+ const SPECIAL_TYPES = new Set(['Object', 'X', 'Bool', 'Vector t', 'true', '#'])
21
+
22
+ function tsName(name: string): string {
23
+ return name
24
+ .split('.')
25
+ .map(s => (s ? s.charAt(0).toUpperCase() + s.slice(1) : s))
26
+ .join('')
27
+ }
28
+
29
+ function mapType(t: TlType, known: Set<string>): string {
30
+ switch (t.kind) {
31
+ case 'int':
32
+ case 'double':
33
+ case 'flags':
34
+ return 'number'
35
+ case 'long':
36
+ return 'bigint'
37
+ case 'bytes':
38
+ case 'int128':
39
+ case 'int256':
40
+ return 'Buffer'
41
+ case 'string':
42
+ return 'string'
43
+ case 'bool':
44
+ return 'boolean'
45
+ case 'true':
46
+ return 'true'
47
+ case 'vector':
48
+ return `${wrapUnion(mapType(t.inner, known))}[]`
49
+ case 'object':
50
+ return 'unknown'
51
+ case 'flag':
52
+ return mapType(t.inner, known)
53
+ case 'boxed':
54
+ case 'bare': {
55
+ const n = tsName(t.name)
56
+ return known.has(n) ? n : 'unknown'
57
+ }
58
+ }
59
+ }
60
+
61
+ function wrapUnion(t: string): string {
62
+ return t.includes('|') ? `(${t})` : t
63
+ }
64
+
65
+ function fieldLines(def: TlDef, known: Set<string>): string[] {
66
+ const out: string[] = []
67
+ for (const p of def.params) {
68
+ if (p.type.kind === 'flags') continue // internal bitmask
69
+ const prop = /^[A-Za-z_$][\w$]*$/.test(p.name) ? p.name : JSON.stringify(p.name)
70
+ if (p.type.kind === 'flag') {
71
+ out.push(` ${prop}?: ${mapType(p.type.inner, known)}`)
72
+ } else {
73
+ out.push(` ${prop}: ${mapType(p.type, known)}`)
74
+ }
75
+ }
76
+ return out
77
+ }
78
+
79
+ /**
80
+ * Generates TypeScript types from the `.tl` schema in `dir`: one interface per
81
+ * constructor, a union per type, and the `RpcMethods` map (method → params/result)
82
+ * you pass to `createServer<RpcMethods>()`. Returns the source as a string; see
83
+ * {@link writeSchemaTs} to write it to a file. Run via your app's `gen:types` script.
84
+ */
85
+ export function generateSchemaTs(dir: string = bundledSchemaDir): string {
86
+ const { defs } = parseSchemaDir(dir)
87
+
88
+ const ctors = defs.filter(d => d.kind === 'constructor')
89
+ const methods = defs.filter(d => d.kind === 'method')
90
+
91
+ // Type groups (union of constructors), excluding special types.
92
+ const typeGroups = new Map<string, TlDef[]>()
93
+ for (const d of ctors) {
94
+ if (SPECIAL_TYPES.has(d.type)) continue
95
+ const g = typeGroups.get(d.type) ?? []
96
+ g.push(d)
97
+ typeGroups.set(d.type, g)
98
+ }
99
+
100
+ // A TL type is a UNION of its constructors, emitted under the bare type name.
101
+ // The one exception: a type with a single constructor whose name equals the
102
+ // type name (e.g. `error` = Error) — there the lone interface IS the type, so
103
+ // no union alias is needed.
104
+ const unions = new Map<string, TlDef[]>()
105
+ for (const [type, group] of typeGroups) {
106
+ const name = tsName(type)
107
+ const soleSelfNamed = group.length === 1 && tsName(group[0]!.name) === name
108
+ if (soleSelfNamed) continue
109
+ unions.set(name, group)
110
+ }
111
+ const unionNames = new Set(unions.keys())
112
+
113
+ // A constructor's interface name. When it would take a name a union already
114
+ // owns (the type's "primary" constructor — e.g. `user` in type `User`), suffix
115
+ // it with `_` so the union keeps the bare name: `User = User_ | UserEmpty | …`.
116
+ const ctorIface = (d: TlDef): string => {
117
+ const base = tsName(d.name)
118
+ return unionNames.has(base) ? `${base}_` : base
119
+ }
120
+
121
+ // Interface def per constructor name (dedup; first wins).
122
+ const ctorByTs = new Map<string, TlDef>()
123
+ for (const d of ctors) {
124
+ const name = ctorIface(d)
125
+ if (!ctorByTs.has(name)) ctorByTs.set(name, d)
126
+ }
127
+
128
+ const known = new Set<string>([...ctorByTs.keys(), ...unionNames])
129
+
130
+ const lines: string[] = [
131
+ '// AUTO-GENERATED by `yarn gen:types`. Do not edit by hand.',
132
+ '// Types for the newest schema layer (post-fromJson shapes).',
133
+ '/* eslint-disable */',
134
+ '',
135
+ ]
136
+
137
+ // Constructor interfaces (sorted).
138
+ for (const name of [...ctorByTs.keys()].sort()) {
139
+ const def = ctorByTs.get(name)!
140
+ const fields = fieldLines(def, known)
141
+ lines.push(`export interface ${name} {`)
142
+ lines.push(` _: ${JSON.stringify(def.name)}`)
143
+ lines.push(...fields)
144
+ lines.push('}', '')
145
+ }
146
+
147
+ // Type unions (sorted).
148
+ for (const name of [...unions.keys()].sort()) {
149
+ const members = [...new Set(unions.get(name)!.map(d => ctorIface(d)))]
150
+ lines.push(`export type ${name} = ${members.join(' | ')}`, '')
151
+ }
152
+
153
+ // RpcMethods map (sorted by method name; dedup by name — first wins).
154
+ lines.push('export interface RpcMethods {')
155
+ const seenMethods = new Set<string>()
156
+ for (const m of [...methods].sort((a, b) => a.name.localeCompare(b.name))) {
157
+ if (seenMethods.has(m.name)) continue
158
+ seenMethods.add(m.name)
159
+ const paramsName = `${tsName(m.name)}Params`
160
+ const result = resultType(m.type, known)
161
+ lines.push(` ${JSON.stringify(m.name)}: { params: ${paramsName}; result: ${result} }`)
162
+ }
163
+ lines.push('}', '')
164
+
165
+ // Method params interfaces (sorted).
166
+ const seenParams = new Set<string>()
167
+ for (const m of [...methods].sort((a, b) => a.name.localeCompare(b.name))) {
168
+ const paramsName = `${tsName(m.name)}Params`
169
+ if (seenParams.has(paramsName)) continue
170
+ seenParams.add(paramsName)
171
+ lines.push(`export interface ${paramsName} {`)
172
+ lines.push(...fieldLines(m, known))
173
+ lines.push('}', '')
174
+ }
175
+
176
+ return lines.join('\n')
177
+ }
178
+
179
+ function resultType(type: string, known: Set<string>): string {
180
+ if (type === 'Bool') return 'boolean'
181
+ if (type === 'Object' || type === 'X') return 'unknown'
182
+ const n = tsName(type)
183
+ return known.has(n) ? n : 'unknown'
184
+ }
185
+
186
+ /** Generates types for the schema in `schemaDir` and writes them to `outPath`. */
187
+ export function writeSchemaTs(schemaDir: string, outPath: string): void {
188
+ mkdirSync(dirname(outPath), { recursive: true })
189
+ writeFileSync(outPath, generateSchemaTs(schemaDir))
190
+ console.log(`Generated -> ${outPath}`)
191
+ }
192
+
193
+ // CLI: `tsx gen-types.ts <schemaDir> <outPath>` (apps wrap this in their own
194
+ // `gen:types` script pointing at their schema). No bundled default — the
195
+ // framework no longer ships business types.
196
+ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
197
+ const [schemaDir, outPath] = process.argv.slice(2)
198
+ if (!schemaDir || !outPath) {
199
+ console.error('usage: gen-types <schemaDir> <outPath>')
200
+ process.exit(1)
201
+ }
202
+ writeSchemaTs(schemaDir, outPath)
203
+ }