@vishalsharma7nov/react-native-proto 0.2.2

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 (190) hide show
  1. package/android/src/main/java/com/vishalsharma7nov/reactnativeproto/ReactNativeProtoNativeGrpcModule.java +142 -0
  2. package/app.plugin.js +63 -0
  3. package/bin/react-native-proto.js +956 -0
  4. package/ios/ReactNativeProtoNativeGrpc.m +129 -0
  5. package/lib/commonjs/auth.js +72 -0
  6. package/lib/commonjs/auth.js.map +1 -0
  7. package/lib/commonjs/client-factory.js +81 -0
  8. package/lib/commonjs/client-factory.js.map +1 -0
  9. package/lib/commonjs/codecs.js +41 -0
  10. package/lib/commonjs/codecs.js.map +1 -0
  11. package/lib/commonjs/connect/envelope.js +100 -0
  12. package/lib/commonjs/connect/envelope.js.map +1 -0
  13. package/lib/commonjs/connect/errors.js +71 -0
  14. package/lib/commonjs/connect/errors.js.map +1 -0
  15. package/lib/commonjs/errors.js +54 -0
  16. package/lib/commonjs/errors.js.map +1 -0
  17. package/lib/commonjs/generated/create-api.js +29 -0
  18. package/lib/commonjs/generated/create-api.js.map +1 -0
  19. package/lib/commonjs/generated/message-types.js +2 -0
  20. package/lib/commonjs/generated/message-types.js.map +1 -0
  21. package/lib/commonjs/generated/messages.js +47 -0
  22. package/lib/commonjs/generated/messages.js.map +1 -0
  23. package/lib/commonjs/generated/messages.pb.js +2787 -0
  24. package/lib/commonjs/generated/messages.pb.js.map +1 -0
  25. package/lib/commonjs/generated/method-map.js +68 -0
  26. package/lib/commonjs/generated/method-map.js.map +1 -0
  27. package/lib/commonjs/index.js +283 -0
  28. package/lib/commonjs/index.js.map +1 -0
  29. package/lib/commonjs/interceptors.js +19 -0
  30. package/lib/commonjs/interceptors.js.map +1 -0
  31. package/lib/commonjs/logging.js +54 -0
  32. package/lib/commonjs/logging.js.map +1 -0
  33. package/lib/commonjs/metro/index.js +35 -0
  34. package/lib/commonjs/metro/index.js.map +1 -0
  35. package/lib/commonjs/metro/transformer.js +33 -0
  36. package/lib/commonjs/metro/transformer.js.map +1 -0
  37. package/lib/commonjs/mock-server/index.js +125 -0
  38. package/lib/commonjs/mock-server/index.js.map +1 -0
  39. package/lib/commonjs/native-grpc/index.js +242 -0
  40. package/lib/commonjs/native-grpc/index.js.map +1 -0
  41. package/lib/commonjs/offline-queue.js +137 -0
  42. package/lib/commonjs/offline-queue.js.map +1 -0
  43. package/lib/commonjs/package.json +1 -0
  44. package/lib/commonjs/path-templates.js +44 -0
  45. package/lib/commonjs/path-templates.js.map +1 -0
  46. package/lib/commonjs/react-query/index.js +30 -0
  47. package/lib/commonjs/react-query/index.js.map +1 -0
  48. package/lib/commonjs/streaming.js +251 -0
  49. package/lib/commonjs/streaming.js.map +1 -0
  50. package/lib/commonjs/transport.js +323 -0
  51. package/lib/commonjs/transport.js.map +1 -0
  52. package/lib/commonjs/types.js +6 -0
  53. package/lib/commonjs/types.js.map +1 -0
  54. package/lib/commonjs/validation/zod.js +48 -0
  55. package/lib/commonjs/validation/zod.js.map +1 -0
  56. package/lib/commonjs/version-check.js +102 -0
  57. package/lib/commonjs/version-check.js.map +1 -0
  58. package/lib/module/auth.js +67 -0
  59. package/lib/module/auth.js.map +1 -0
  60. package/lib/module/client-factory.js +77 -0
  61. package/lib/module/client-factory.js.map +1 -0
  62. package/lib/module/codecs.js +36 -0
  63. package/lib/module/codecs.js.map +1 -0
  64. package/lib/module/connect/envelope.js +93 -0
  65. package/lib/module/connect/envelope.js.map +1 -0
  66. package/lib/module/connect/errors.js +65 -0
  67. package/lib/module/connect/errors.js.map +1 -0
  68. package/lib/module/errors.js +46 -0
  69. package/lib/module/errors.js.map +1 -0
  70. package/lib/module/generated/create-api.js +13 -0
  71. package/lib/module/generated/create-api.js.map +1 -0
  72. package/lib/module/generated/message-types.js +2 -0
  73. package/lib/module/generated/message-types.js.map +1 -0
  74. package/lib/module/generated/messages.js +43 -0
  75. package/lib/module/generated/messages.js.map +1 -0
  76. package/lib/module/generated/messages.pb.js +2787 -0
  77. package/lib/module/generated/messages.pb.js.map +1 -0
  78. package/lib/module/generated/method-map.js +64 -0
  79. package/lib/module/generated/method-map.js.map +1 -0
  80. package/lib/module/index.js +53 -0
  81. package/lib/module/index.js.map +1 -0
  82. package/lib/module/interceptors.js +15 -0
  83. package/lib/module/interceptors.js.map +1 -0
  84. package/lib/module/logging.js +50 -0
  85. package/lib/module/logging.js.map +1 -0
  86. package/lib/module/metro/index.js +25 -0
  87. package/lib/module/metro/index.js.map +1 -0
  88. package/lib/module/metro/transformer.js +29 -0
  89. package/lib/module/metro/transformer.js.map +1 -0
  90. package/lib/module/mock-server/index.js +120 -0
  91. package/lib/module/mock-server/index.js.map +1 -0
  92. package/lib/module/native-grpc/index.js +233 -0
  93. package/lib/module/native-grpc/index.js.map +1 -0
  94. package/lib/module/offline-queue.js +132 -0
  95. package/lib/module/offline-queue.js.map +1 -0
  96. package/lib/module/package.json +1 -0
  97. package/lib/module/path-templates.js +39 -0
  98. package/lib/module/path-templates.js.map +1 -0
  99. package/lib/module/react-query/index.js +25 -0
  100. package/lib/module/react-query/index.js.map +1 -0
  101. package/lib/module/streaming.js +246 -0
  102. package/lib/module/streaming.js.map +1 -0
  103. package/lib/module/transport.js +316 -0
  104. package/lib/module/transport.js.map +1 -0
  105. package/lib/module/types.js +4 -0
  106. package/lib/module/types.js.map +1 -0
  107. package/lib/module/validation/zod.js +43 -0
  108. package/lib/module/validation/zod.js.map +1 -0
  109. package/lib/module/version-check.js +93 -0
  110. package/lib/module/version-check.js.map +1 -0
  111. package/lib/typescript/src/auth.d.ts +26 -0
  112. package/lib/typescript/src/auth.d.ts.map +1 -0
  113. package/lib/typescript/src/client-factory.d.ts +7 -0
  114. package/lib/typescript/src/client-factory.d.ts.map +1 -0
  115. package/lib/typescript/src/codecs.d.ts +5 -0
  116. package/lib/typescript/src/codecs.d.ts.map +1 -0
  117. package/lib/typescript/src/connect/envelope.d.ts +18 -0
  118. package/lib/typescript/src/connect/envelope.d.ts.map +1 -0
  119. package/lib/typescript/src/connect/errors.d.ts +11 -0
  120. package/lib/typescript/src/connect/errors.d.ts.map +1 -0
  121. package/lib/typescript/src/errors.d.ts +21 -0
  122. package/lib/typescript/src/errors.d.ts.map +1 -0
  123. package/lib/typescript/src/generated/create-api.d.ts +34 -0
  124. package/lib/typescript/src/generated/create-api.d.ts.map +1 -0
  125. package/lib/typescript/src/generated/message-types.d.ts +54 -0
  126. package/lib/typescript/src/generated/message-types.d.ts.map +1 -0
  127. package/lib/typescript/src/generated/messages.d.ts +17 -0
  128. package/lib/typescript/src/generated/messages.d.ts.map +1 -0
  129. package/lib/typescript/src/generated/method-map.d.ts +7 -0
  130. package/lib/typescript/src/generated/method-map.d.ts.map +1 -0
  131. package/lib/typescript/src/index.d.ts +53 -0
  132. package/lib/typescript/src/index.d.ts.map +1 -0
  133. package/lib/typescript/src/interceptors.d.ts +17 -0
  134. package/lib/typescript/src/interceptors.d.ts.map +1 -0
  135. package/lib/typescript/src/logging.d.ts +16 -0
  136. package/lib/typescript/src/logging.d.ts.map +1 -0
  137. package/lib/typescript/src/metro/index.d.ts +15 -0
  138. package/lib/typescript/src/metro/index.d.ts.map +1 -0
  139. package/lib/typescript/src/metro/transformer.d.ts +16 -0
  140. package/lib/typescript/src/metro/transformer.d.ts.map +1 -0
  141. package/lib/typescript/src/mock-server/index.d.ts +27 -0
  142. package/lib/typescript/src/mock-server/index.d.ts.map +1 -0
  143. package/lib/typescript/src/native-grpc/index.d.ts +36 -0
  144. package/lib/typescript/src/native-grpc/index.d.ts.map +1 -0
  145. package/lib/typescript/src/offline-queue.d.ts +47 -0
  146. package/lib/typescript/src/offline-queue.d.ts.map +1 -0
  147. package/lib/typescript/src/path-templates.d.ts +17 -0
  148. package/lib/typescript/src/path-templates.d.ts.map +1 -0
  149. package/lib/typescript/src/react-query/index.d.ts +14 -0
  150. package/lib/typescript/src/react-query/index.d.ts.map +1 -0
  151. package/lib/typescript/src/streaming.d.ts +10 -0
  152. package/lib/typescript/src/streaming.d.ts.map +1 -0
  153. package/lib/typescript/src/transport.d.ts +19 -0
  154. package/lib/typescript/src/transport.d.ts.map +1 -0
  155. package/lib/typescript/src/types.d.ts +78 -0
  156. package/lib/typescript/src/types.d.ts.map +1 -0
  157. package/lib/typescript/src/validation/zod.d.ts +33 -0
  158. package/lib/typescript/src/validation/zod.d.ts.map +1 -0
  159. package/lib/typescript/src/version-check.d.ts +40 -0
  160. package/lib/typescript/src/version-check.d.ts.map +1 -0
  161. package/metro/index.js +23 -0
  162. package/metro/transformer.js +21 -0
  163. package/package.json +144 -0
  164. package/react-native-proto.podspec +15 -0
  165. package/src/auth.ts +97 -0
  166. package/src/client-factory.ts +109 -0
  167. package/src/codecs.ts +42 -0
  168. package/src/connect/envelope.ts +112 -0
  169. package/src/connect/errors.ts +86 -0
  170. package/src/errors.ts +81 -0
  171. package/src/generated/create-api.ts +40 -0
  172. package/src/generated/message-types.ts +63 -0
  173. package/src/generated/messages.pb.js +3207 -0
  174. package/src/generated/messages.ts +45 -0
  175. package/src/generated/method-map.ts +71 -0
  176. package/src/index.ts +113 -0
  177. package/src/interceptors.ts +32 -0
  178. package/src/logging.ts +67 -0
  179. package/src/metro/index.ts +27 -0
  180. package/src/metro/transformer.ts +38 -0
  181. package/src/mock-server/index.ts +164 -0
  182. package/src/native-grpc/index.ts +302 -0
  183. package/src/offline-queue.ts +160 -0
  184. package/src/path-templates.ts +65 -0
  185. package/src/react-query/index.ts +40 -0
  186. package/src/streaming.ts +326 -0
  187. package/src/transport.ts +427 -0
  188. package/src/types.ts +94 -0
  189. package/src/validation/zod.ts +75 -0
  190. package/src/version-check.ts +121 -0
@@ -0,0 +1,956 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * react-native-proto CLI — generate clients from GitHub, Buf, or local .proto files.
6
+ * Usage:
7
+ * react-native-proto generate --from local --path ./protos --out ./src/generated
8
+ * react-native-proto generate --from github --repo https://github.com/org/protos.git --ref v1.0.0 --out ./src/generated
9
+ * react-native-proto generate --from buf --module buf.build/owner/name --ref v1.0.0 --out ./src/generated
10
+ * react-native-proto generate --from local --path ./protos --out ./src/generated --import-from @vishalsharma7nov/react-native-proto
11
+ * react-native-proto sync (reads protos.lock.json from cwd)
12
+ * react-native-proto watch (local proto dir; lock or same flags as generate)
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const os = require('os');
18
+ const { spawnSync } = require('child_process');
19
+
20
+ const MAX_PROTO_FILES = 500;
21
+ const WATCH_DEBOUNCE_MS = 300;
22
+
23
+ class CliError extends Error {
24
+ constructor(message, code = 1) {
25
+ super(message);
26
+ this.name = 'CliError';
27
+ this.code = code;
28
+ }
29
+ }
30
+
31
+ function printHelp() {
32
+ console.log(`react-native-proto — React Native protobuf tooling
33
+
34
+ Commands:
35
+ generate Generate method-map, messages, message-types, create-api from .proto files
36
+ sync Read protos.lock.json and generate
37
+ watch Watch a local proto directory and re-generate on changes
38
+ help Show this help
39
+
40
+ Options for generate / watch:
41
+ --from local|github|buf
42
+ --path <dir> Local proto directory (local)
43
+ --repo <git-url> Git repository URL (github)
44
+ --module <buf-module> Buf module, e.g. buf.build/owner/name (buf)
45
+ --ref <tag|commit|ver> Git ref (github) or Buf version (buf)
46
+ --out <dir> Output directory (default: ./src/generated)
47
+ --import-from <pkg> Consumer mode: import runtime helpers from this package
48
+ (e.g. @vishalsharma7nov/react-native-proto)
49
+ --help, -h
50
+
51
+ Examples:
52
+ react-native-proto generate --from local --path ./vendor/protos --out ./src/generated
53
+ react-native-proto generate --from buf --module buf.build/acme/petapis --ref 1.0.0 --out ./src/generated
54
+ react-native-proto generate --from local --path ./protos --out ./src/api/generated --import-from @vishalsharma7nov/react-native-proto
55
+ react-native-proto sync
56
+ react-native-proto watch
57
+ react-native-proto watch --from local --path ./vendor/protos --out ./src/generated
58
+ `);
59
+ }
60
+
61
+ function fail(message, code = 1) {
62
+ throw new CliError(message, code);
63
+ }
64
+
65
+ function emptyArgs() {
66
+ return {
67
+ command: '',
68
+ from: '',
69
+ path: '',
70
+ repo: '',
71
+ module: '',
72
+ ref: '',
73
+ out: './src/generated',
74
+ importFrom: '',
75
+ };
76
+ }
77
+
78
+ function parseArgs(argv) {
79
+ const args = emptyArgs();
80
+
81
+ if (argv.length === 0 || argv.includes('--help') || argv.includes('-h')) {
82
+ printHelp();
83
+ process.exit(0);
84
+ }
85
+
86
+ args.command = argv[0] || '';
87
+ if (args.command === 'help') {
88
+ printHelp();
89
+ process.exit(0);
90
+ }
91
+
92
+ for (let i = 1; i < argv.length; i += 1) {
93
+ const token = argv[i];
94
+ const next = argv[i + 1];
95
+ if (token === '--from' && next) {
96
+ args.from = next;
97
+ i += 1;
98
+ } else if (token === '--path' && next) {
99
+ args.path = next;
100
+ i += 1;
101
+ } else if (token === '--repo' && next) {
102
+ args.repo = next;
103
+ i += 1;
104
+ } else if (token === '--module' && next) {
105
+ args.module = next;
106
+ i += 1;
107
+ } else if (token === '--ref' && next) {
108
+ args.ref = next;
109
+ i += 1;
110
+ } else if (token === '--out' && next) {
111
+ args.out = next;
112
+ i += 1;
113
+ } else if (token === '--import-from' && next) {
114
+ args.importFrom = next;
115
+ i += 1;
116
+ } else {
117
+ fail(`Unknown or incomplete argument: ${token}`);
118
+ }
119
+ }
120
+ return args;
121
+ }
122
+
123
+ function stripProtoComments(content) {
124
+ return content
125
+ .replace(/\/\*[\s\S]*?\*\//g, '')
126
+ .replace(/\/\/.*$/gm, '');
127
+ }
128
+
129
+ function listProtoFiles(dir) {
130
+ const absolute = path.resolve(dir);
131
+ if (!fs.existsSync(absolute)) {
132
+ fail(`Proto path does not exist: ${absolute}`);
133
+ }
134
+
135
+ const results = [];
136
+
137
+ function walk(current) {
138
+ if (results.length >= MAX_PROTO_FILES) {
139
+ fail(`Too many .proto files (max ${MAX_PROTO_FILES})`);
140
+ }
141
+ const entries = fs.readdirSync(current, { withFileTypes: true });
142
+ for (let i = 0; i < entries.length; i += 1) {
143
+ const entry = entries[i];
144
+ if (!entry) continue;
145
+ const full = path.join(current, entry.name);
146
+ if (entry.isDirectory()) {
147
+ walk(full);
148
+ } else if (entry.isFile() && entry.name.endsWith('.proto')) {
149
+ results.push(full);
150
+ }
151
+ }
152
+ }
153
+
154
+ walk(absolute);
155
+ if (results.length === 0) {
156
+ fail(`No .proto files found under ${absolute}`);
157
+ }
158
+ return results;
159
+ }
160
+
161
+ function stageFromGithub(repo, ref) {
162
+ if (!repo) fail('--repo is required when --from github');
163
+ if (!ref) fail('--ref is required when --from github (pin a tag or commit)');
164
+
165
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'react-native-proto-'));
166
+ const target = path.join(tmp, 'protos');
167
+ const clone = spawnSync(
168
+ 'git',
169
+ ['clone', '--depth', '1', '--branch', ref, repo, target],
170
+ { encoding: 'utf8' }
171
+ );
172
+
173
+ if (clone.status !== 0) {
174
+ // fallback: clone default then checkout ref (for commit SHAs)
175
+ const clone2 = spawnSync('git', ['clone', repo, target], { encoding: 'utf8' });
176
+ if (clone2.status !== 0) {
177
+ fail(`git clone failed: ${clone.stderr || clone2.stderr}`);
178
+ }
179
+ const checkout = spawnSync('git', ['checkout', ref], {
180
+ cwd: target,
181
+ encoding: 'utf8',
182
+ });
183
+ if (checkout.status !== 0) {
184
+ fail(`git checkout ${ref} failed: ${checkout.stderr}`);
185
+ }
186
+ }
187
+
188
+ return target;
189
+ }
190
+
191
+ function stageFromBuf(moduleName, ref) {
192
+ if (!moduleName) fail('--module is required when --from buf');
193
+
194
+ const tmp = fs.mkdtempSync(path.join(os.tmpdir(), 'react-native-proto-buf-'));
195
+ const moduleRef =
196
+ ref && !moduleName.includes(':') ? `${moduleName}:${ref}` : moduleName;
197
+
198
+ const exportArgs = ['export', moduleRef, '--output', tmp];
199
+
200
+ let result = spawnSync('npx', ['--no-install', 'buf', ...exportArgs], {
201
+ encoding: 'utf8',
202
+ });
203
+
204
+ if (result.status !== 0) {
205
+ result = spawnSync('buf', exportArgs, { encoding: 'utf8' });
206
+ }
207
+
208
+ if (result.status !== 0) {
209
+ const detail = (result.stderr || result.stdout || '').trim();
210
+ const notFound =
211
+ result.error ||
212
+ /not found|ENOENT|command not found/i.test(detail) ||
213
+ (result.status === 127);
214
+
215
+ if (notFound || (!detail && result.status !== 0)) {
216
+ // Prefer install guidance when the CLI itself is missing
217
+ const probe = spawnSync('buf', ['--version'], { encoding: 'utf8' });
218
+ const npxProbe = spawnSync('npx', ['--no-install', 'buf', '--version'], {
219
+ encoding: 'utf8',
220
+ });
221
+ if (probe.status !== 0 && npxProbe.status !== 0) {
222
+ fail(
223
+ 'buf CLI not found. Install Buf (https://buf.build/docs/installation) and ensure `buf` is on PATH, or add it as a local dependency so `npx --no-install buf` works.'
224
+ );
225
+ }
226
+ }
227
+
228
+ fail(
229
+ [
230
+ `buf export failed for "${moduleRef}".`,
231
+ detail ? `Details: ${detail}` : null,
232
+ ]
233
+ .filter(Boolean)
234
+ .join(' ')
235
+ );
236
+ }
237
+
238
+ return tmp;
239
+ }
240
+
241
+ function parseProtoServices(fileContent, filePath) {
242
+ const methods = [];
243
+ let packageName = '';
244
+ const packageMatch = fileContent.match(/package\s+([A-Za-z0-9_.]+)\s*;/);
245
+ if (packageMatch) {
246
+ packageName = packageMatch[1];
247
+ }
248
+
249
+ const cleaned = stripProtoComments(fileContent);
250
+ const serviceRegex = /service\s+(\w+)\s*\{([\s\S]*?)\}/g;
251
+ let serviceMatch = serviceRegex.exec(cleaned);
252
+ while (serviceMatch) {
253
+ const serviceName = serviceMatch[1];
254
+ const body = serviceMatch[2] || '';
255
+ const rpcRegex =
256
+ /rpc\s+(\w+)\s*\(\s*(stream\s+)?([\w.]+)\s*\)\s*returns\s*\(\s*(stream\s+)?([\w.]+)\s*\)\s*;/g;
257
+ let rpcMatch = rpcRegex.exec(body);
258
+ while (rpcMatch) {
259
+ const methodName = rpcMatch[1];
260
+ const clientStream = Boolean(rpcMatch[2]);
261
+ const requestTypeRaw = rpcMatch[3];
262
+ const serverStream = Boolean(rpcMatch[4]);
263
+ const responseTypeRaw = rpcMatch[5];
264
+
265
+ let kind = 'unary';
266
+ if (clientStream && serverStream) kind = 'bidi';
267
+ else if (clientStream) kind = 'clientStreaming';
268
+ else if (serverStream) kind = 'serverStreaming';
269
+
270
+ const qualify = (name) => {
271
+ if (name.includes('.')) return name;
272
+ return packageName ? `${packageName}.${name}` : name;
273
+ };
274
+
275
+ methods.push({
276
+ serviceName,
277
+ methodName,
278
+ packageName,
279
+ rpcPath: `${packageName ? `${packageName}.` : ''}${serviceName}/${methodName}`,
280
+ requestType: qualify(requestTypeRaw),
281
+ responseType: qualify(responseTypeRaw),
282
+ kind,
283
+ source: filePath,
284
+ });
285
+
286
+ rpcMatch = rpcRegex.exec(body);
287
+ }
288
+ serviceMatch = serviceRegex.exec(cleaned);
289
+ }
290
+
291
+ return methods;
292
+ }
293
+
294
+ const SCALAR_TO_TS = {
295
+ double: 'number',
296
+ float: 'number',
297
+ int32: 'number',
298
+ int64: 'number',
299
+ uint32: 'number',
300
+ uint64: 'number',
301
+ sint32: 'number',
302
+ sint64: 'number',
303
+ fixed32: 'number',
304
+ fixed64: 'number',
305
+ sfixed32: 'number',
306
+ sfixed64: 'number',
307
+ bool: 'boolean',
308
+ string: 'string',
309
+ bytes: 'Uint8Array',
310
+ };
311
+
312
+ function protoTypeToTs(typeName) {
313
+ if (SCALAR_TO_TS[typeName]) {
314
+ return SCALAR_TO_TS[typeName];
315
+ }
316
+ // message / enum reference — use simple type name (last segment)
317
+ const parts = typeName.split('.');
318
+ return parts[parts.length - 1];
319
+ }
320
+
321
+ function simpleTypeName(qualified) {
322
+ const parts = String(qualified).split('.');
323
+ return parts[parts.length - 1];
324
+ }
325
+
326
+ /**
327
+ * Simple regex parser for `message Name { fields }` including repeated and map.
328
+ */
329
+ function parseProtoMessages(fileContent) {
330
+ const messages = [];
331
+ const cleaned = stripProtoComments(fileContent);
332
+
333
+ let packageName = '';
334
+ const packageMatch = cleaned.match(/package\s+([A-Za-z0-9_.]+)\s*;/);
335
+ if (packageMatch) {
336
+ packageName = packageMatch[1];
337
+ }
338
+
339
+ const messageRegex = /message\s+(\w+)\s*\{([\s\S]*?)\}/g;
340
+ let messageMatch = messageRegex.exec(cleaned);
341
+ while (messageMatch) {
342
+ const name = messageMatch[1];
343
+ const body = messageMatch[2] || '';
344
+ const fields = [];
345
+
346
+ const mapRegex =
347
+ /(?:repeated\s+|optional\s+|required\s+)?map\s*<\s*([\w.]+)\s*,\s*([\w.]+)\s*>\s+(\w+)\s*=\s*\d+\s*;/g;
348
+ let mapMatch = mapRegex.exec(body);
349
+ const mapFieldNames = new Set();
350
+ while (mapMatch) {
351
+ const keyTs = protoTypeToTs(mapMatch[1]);
352
+ const valueTs = protoTypeToTs(mapMatch[2]);
353
+ const fieldName = mapMatch[3];
354
+ mapFieldNames.add(fieldName);
355
+ fields.push({
356
+ name: fieldName,
357
+ tsType: `Record<${keyTs}, ${valueTs}>`,
358
+ });
359
+ mapMatch = mapRegex.exec(body);
360
+ }
361
+
362
+ const fieldRegex =
363
+ /(repeated|optional|required)?\s*([\w.]+)\s+(\w+)\s*=\s*\d+\s*;/g;
364
+ let fieldMatch = fieldRegex.exec(body);
365
+ while (fieldMatch) {
366
+ const label = fieldMatch[1] || '';
367
+ const typeName = fieldMatch[2];
368
+ const fieldName = fieldMatch[3];
369
+ if (mapFieldNames.has(fieldName)) {
370
+ fieldMatch = fieldRegex.exec(body);
371
+ continue;
372
+ }
373
+ // Skip nested message/enum declarations mistaken as fields
374
+ if (typeName === 'message' || typeName === 'enum' || typeName === 'oneof') {
375
+ fieldMatch = fieldRegex.exec(body);
376
+ continue;
377
+ }
378
+ let tsType = protoTypeToTs(typeName);
379
+ if (label === 'repeated') {
380
+ tsType = `${tsType}[]`;
381
+ }
382
+ fields.push({ name: fieldName, tsType });
383
+ fieldMatch = fieldRegex.exec(body);
384
+ }
385
+
386
+ messages.push({
387
+ name,
388
+ packageName,
389
+ fullName: packageName ? `${packageName}.${name}` : name,
390
+ fields,
391
+ });
392
+
393
+ messageMatch = messageRegex.exec(cleaned);
394
+ }
395
+
396
+ return messages;
397
+ }
398
+
399
+ function toCamelCase(name) {
400
+ if (!name) return name;
401
+ return name.charAt(0).toLowerCase() + name.slice(1);
402
+ }
403
+
404
+ function resolveImportPaths(importFrom) {
405
+ if (importFrom) {
406
+ return {
407
+ methodMapType: `import type { MethodMap } from '${importFrom}';`,
408
+ codecsImport: `import { createCodecRegistry } from '${importFrom}';`,
409
+ createApiImports: `import { createClientFromMap } from '${importFrom}';\nimport type { ProtoClientConfig } from '${importFrom}';`,
410
+ };
411
+ }
412
+ return {
413
+ methodMapType: `import type { MethodMap } from '../types';`,
414
+ codecsImport: `import { createCodecRegistry } from '../codecs';`,
415
+ createApiImports: `import { createClientFromMap } from '../client-factory';\nimport type { ProtoClientConfig } from '../types';`,
416
+ };
417
+ }
418
+
419
+ function writeMessageTypes(outDir, messages) {
420
+ const lines = [
421
+ '/**',
422
+ ' * Auto-generated TypeScript message types. Do not edit by hand.',
423
+ ' * Parsed from .proto message field definitions (simple regex parser).',
424
+ ' */',
425
+ '',
426
+ ];
427
+
428
+ // Deduplicate by simple name (last wins if conflicts)
429
+ const byName = new Map();
430
+ for (let i = 0; i < messages.length; i += 1) {
431
+ byName.set(messages[i].name, messages[i]);
432
+ }
433
+
434
+ const sorted = Array.from(byName.values()).sort((a, b) =>
435
+ a.name.localeCompare(b.name)
436
+ );
437
+
438
+ for (let i = 0; i < sorted.length; i += 1) {
439
+ const msg = sorted[i];
440
+ lines.push(`export type ${msg.name} = {`);
441
+ for (let j = 0; j < msg.fields.length; j += 1) {
442
+ const field = msg.fields[j];
443
+ lines.push(` ${field.name}?: ${field.tsType};`);
444
+ }
445
+ lines.push('};');
446
+ lines.push('');
447
+ }
448
+
449
+ fs.writeFileSync(path.join(outDir, 'message-types.ts'), lines.join('\n'), 'utf8');
450
+ }
451
+
452
+ function writeCreateApi(outDir, methods, messages, importFrom) {
453
+ const imports = resolveImportPaths(importFrom);
454
+ const messageNames = Array.from(
455
+ new Set(messages.map((m) => m.name))
456
+ ).sort();
457
+
458
+ // Group methods by service
459
+ const byService = new Map();
460
+ for (let i = 0; i < methods.length; i += 1) {
461
+ const m = methods[i];
462
+ const list = byService.get(m.serviceName) || [];
463
+ list.push(m);
464
+ byService.set(m.serviceName, list);
465
+ }
466
+
467
+ const typeImport =
468
+ messageNames.length > 0
469
+ ? `import type {\n ${messageNames.join(',\n ')},\n} from './message-types';\n`
470
+ : '';
471
+
472
+ const lines = [
473
+ '/**',
474
+ ' * Auto-generated typed API factory. Do not edit by hand.',
475
+ ' */',
476
+ imports.createApiImports,
477
+ `import { methodMap } from './method-map';`,
478
+ `import { defaultCodecs } from './messages';`,
479
+ typeImport,
480
+ ];
481
+
482
+ const serviceInterfaceNames = [];
483
+ const serviceEntries = Array.from(byService.entries()).sort((a, b) =>
484
+ a[0].localeCompare(b[0])
485
+ );
486
+
487
+ for (let i = 0; i < serviceEntries.length; i += 1) {
488
+ const [serviceName, serviceMethods] = serviceEntries[i];
489
+ const iface = `${serviceName}Api`;
490
+ serviceInterfaceNames.push({
491
+ serviceName,
492
+ camel: toCamelCase(serviceName),
493
+ iface,
494
+ });
495
+
496
+ lines.push(`export type ${iface} = {`);
497
+ for (let j = 0; j < serviceMethods.length; j += 1) {
498
+ const m = serviceMethods[j];
499
+ const methodKey = toCamelCase(m.methodName);
500
+ const req = simpleTypeName(m.requestType);
501
+ const res = simpleTypeName(m.responseType);
502
+ if (m.kind === 'unary') {
503
+ lines.push(
504
+ ` ${methodKey}: (request?: ${req}, options?: { signal?: AbortSignal }) => Promise<${res}>;`
505
+ );
506
+ } else {
507
+ lines.push(
508
+ ` ${methodKey}: (request?: ${req}, options?: { signal?: AbortSignal }) => AsyncIterable<${res}>;`
509
+ );
510
+ }
511
+ }
512
+ lines.push('};');
513
+ lines.push('');
514
+ }
515
+
516
+ lines.push('export type GeneratedApi = {');
517
+ for (let i = 0; i < serviceInterfaceNames.length; i += 1) {
518
+ const s = serviceInterfaceNames[i];
519
+ lines.push(` ${s.camel}: ${s.iface};`);
520
+ }
521
+ lines.push(' readonly config: ProtoClientConfig;');
522
+ lines.push('};');
523
+ lines.push('');
524
+ lines.push(
525
+ 'export function createApi(config: ProtoClientConfig): GeneratedApi {'
526
+ );
527
+ lines.push(
528
+ ' return createClientFromMap(config, methodMap, defaultCodecs) as unknown as GeneratedApi;'
529
+ );
530
+ lines.push('}');
531
+ lines.push('');
532
+ lines.push('export { methodMap, defaultCodecs };');
533
+ lines.push('');
534
+
535
+ fs.writeFileSync(path.join(outDir, 'create-api.ts'), lines.join('\n'), 'utf8');
536
+ }
537
+
538
+ function pbAccessPath(fullName) {
539
+ const parts = String(fullName).split('.').filter(Boolean);
540
+ if (parts.length === 0) return 'pb';
541
+ return `pb${parts.map((p) => `?.${p}`).join('')}`;
542
+ }
543
+
544
+ function writeGenerated(outDir, protoFiles, methods, messages, importFrom) {
545
+ fs.mkdirSync(outDir, { recursive: true });
546
+
547
+ const paths = resolveImportPaths(importFrom);
548
+
549
+ const methodMapSource = `${paths.methodMapType}
550
+
551
+ /**
552
+ * Auto-generated method map. Do not edit by hand.
553
+ * Sources: ${protoFiles.map((f) => path.basename(f)).join(', ')}
554
+ */
555
+ export const methodMap: MethodMap = ${JSON.stringify(
556
+ methods.map(({ source, ...rest }) => rest),
557
+ null,
558
+ 2
559
+ )};
560
+ `;
561
+
562
+ fs.writeFileSync(path.join(outDir, 'method-map.ts'), methodMapSource, 'utf8');
563
+
564
+ writeMessageTypes(outDir, messages);
565
+ writeCreateApi(outDir, methods, messages, importFrom);
566
+
567
+ // Prefer pbjs when available for full message codecs
568
+ let pbjsBin = null;
569
+ try {
570
+ const cliRoot = path.dirname(require.resolve('protobufjs-cli/package.json'));
571
+ const candidate = path.join(cliRoot, 'bin', 'pbjs');
572
+ if (fs.existsSync(candidate)) {
573
+ pbjsBin = candidate;
574
+ }
575
+ } catch (_err) {
576
+ // fall through
577
+ }
578
+ if (!pbjsBin) {
579
+ const probe = spawnSync('npx', ['--no-install', 'pbjs', '-h'], {
580
+ encoding: 'utf8',
581
+ });
582
+ if (probe.status === 0) {
583
+ pbjsBin = 'pbjs';
584
+ }
585
+ }
586
+
587
+ const messagesJs = path.join(outDir, 'messages.pb.js');
588
+ const relativeProtos = protoFiles;
589
+
590
+ if (pbjsBin) {
591
+ const pbjsArgs = [
592
+ '-t',
593
+ 'static-module',
594
+ '-w',
595
+ 'commonjs',
596
+ '-o',
597
+ messagesJs,
598
+ ...relativeProtos,
599
+ ];
600
+ const useNpx = pbjsBin === 'pbjs';
601
+ const result = spawnSync(
602
+ useNpx ? 'npx' : process.execPath,
603
+ useNpx ? ['--no-install', 'pbjs', ...pbjsArgs] : [pbjsBin, ...pbjsArgs],
604
+ { encoding: 'utf8' }
605
+ );
606
+ if (result.status !== 0) {
607
+ console.warn(
608
+ 'react-native-proto: pbjs failed; writing descriptor stub. Details:',
609
+ result.stderr || result.stdout
610
+ );
611
+ writeMessagesStub(outDir, methods, importFrom);
612
+ } else {
613
+ writeMessagesWrapper(outDir, messages, importFrom);
614
+ }
615
+ } else {
616
+ writeMessagesStub(outDir, methods, importFrom);
617
+ }
618
+
619
+ console.log(
620
+ `react-native-proto: generated ${methods.length} methods → ${path.resolve(outDir)}`
621
+ );
622
+ console.log(
623
+ `react-native-proto: wrote method-map.ts, messages.ts, message-types.ts, create-api.ts`
624
+ );
625
+ }
626
+
627
+ function writeMessagesWrapper(outDir, messages, importFrom) {
628
+ const paths = resolveImportPaths(importFrom);
629
+
630
+ // Deduplicate message names for exports
631
+ const byName = new Map();
632
+ for (let i = 0; i < (messages || []).length; i += 1) {
633
+ byName.set(messages[i].name, messages[i]);
634
+ }
635
+ const sorted = Array.from(byName.values()).sort((a, b) =>
636
+ a.name.localeCompare(b.name)
637
+ );
638
+
639
+ // Message TypeScript shapes live in message-types.ts only.
640
+ // This file exports protobufjs Type codecs with the same names (values).
641
+
642
+ const namedExports = sorted
643
+ .map((m) => {
644
+ const access = pbAccessPath(m.fullName);
645
+ return `export const ${m.name} = ${access} as Type;`;
646
+ })
647
+ .join('\n');
648
+
649
+ // Also export top-level package namespaces when present
650
+ const packages = Array.from(
651
+ new Set(sorted.map((m) => m.packageName).filter(Boolean))
652
+ );
653
+ const packageExports = packages
654
+ .map((pkg) => {
655
+ const root = pkg.split('.')[0];
656
+ return `export const ${root} = pb.${root};`;
657
+ })
658
+ .join('\n');
659
+
660
+ const wrapper = `/* eslint-disable @typescript-eslint/no-require-imports, @typescript-eslint/no-explicit-any */
661
+ import type { Type } from 'protobufjs';
662
+ ${paths.codecsImport}
663
+ // Auto-generated wrapper. Do not edit by hand.
664
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
665
+ const pb = require('./messages.pb.js');
666
+
667
+ function isType(value: unknown): value is Type {
668
+ if (!value) return false;
669
+ if (typeof value !== 'object' && typeof value !== 'function') return false;
670
+ return typeof (value as Type).encode === 'function' && typeof (value as Type).decode === 'function';
671
+ }
672
+
673
+ function collectTypes(obj: any, prefix = '', acc: Record<string, Type> = {}) {
674
+ if (!obj || (typeof obj !== 'object' && typeof obj !== 'function')) return acc;
675
+ if (isType(obj) && prefix) acc[prefix] = obj;
676
+ for (const key of Object.keys(obj)) {
677
+ if (['encode','decode','create','fromObject','toObject','encodeDelimited','decodeDelimited','verify','getTypeUrl'].includes(key)) continue;
678
+ const next = obj[key];
679
+ const nextPrefix = prefix ? \`\${prefix}.\${key}\` : key;
680
+ if (isType(next)) {
681
+ acc[nextPrefix] = next;
682
+ continue;
683
+ }
684
+ if (next && typeof next === 'object') collectTypes(next, nextPrefix, acc);
685
+ }
686
+ return acc;
687
+ }
688
+
689
+ export const messageTypes = collectTypes(pb);
690
+ export const defaultCodecs = createCodecRegistry(messageTypes);
691
+
692
+ ${packageExports}
693
+ ${namedExports}
694
+
695
+ export default pb;
696
+ `;
697
+ fs.writeFileSync(path.join(outDir, 'messages.ts'), wrapper, 'utf8');
698
+ }
699
+
700
+ function writeMessagesStub(outDir, methods, importFrom) {
701
+ const paths = resolveImportPaths(importFrom);
702
+ const typeNames = Array.from(
703
+ new Set(methods.flatMap((m) => [m.requestType, m.responseType]))
704
+ );
705
+ const stub = `import protobuf from 'protobufjs';
706
+ ${paths.codecsImport}
707
+
708
+ /**
709
+ * Stub registry. Run with protobufjs-cli (pbjs) available for full codecs.
710
+ * Types referenced: ${typeNames.join(', ')}
711
+ */
712
+ const root = new protobuf.Root();
713
+ export const messageTypes: Record<string, protobuf.Type> = {};
714
+ export const defaultCodecs = createCodecRegistry(messageTypes);
715
+ export { root };
716
+ `;
717
+ fs.writeFileSync(path.join(outDir, 'messages.ts'), stub, 'utf8');
718
+ }
719
+
720
+ function resolveProtoRoot(args) {
721
+ if (args.from === 'local') {
722
+ if (!args.path) fail('--path is required when --from local');
723
+ return { protoRoot: args.path, cleanup: null };
724
+ }
725
+ if (args.from === 'github') {
726
+ return { protoRoot: stageFromGithub(args.repo, args.ref), cleanup: null };
727
+ }
728
+ if (args.from === 'buf') {
729
+ return {
730
+ protoRoot: stageFromBuf(args.module, args.ref),
731
+ cleanup: null,
732
+ };
733
+ }
734
+ fail('--from must be "local", "github", or "buf"');
735
+ return { protoRoot: '', cleanup: null };
736
+ }
737
+
738
+ function generate(args) {
739
+ const { protoRoot } = resolveProtoRoot(args);
740
+ const protoFiles = listProtoFiles(protoRoot);
741
+ const methods = [];
742
+ const messages = [];
743
+
744
+ for (let i = 0; i < protoFiles.length; i += 1) {
745
+ const file = protoFiles[i];
746
+ const content = fs.readFileSync(file, 'utf8');
747
+ methods.push(...parseProtoServices(content, file));
748
+ messages.push(...parseProtoMessages(content));
749
+ }
750
+
751
+ if (methods.length === 0) {
752
+ fail(
753
+ 'No service/rpc definitions found. Add `service` blocks to your .proto files.'
754
+ );
755
+ }
756
+
757
+ writeGenerated(
758
+ path.resolve(args.out),
759
+ protoFiles,
760
+ methods,
761
+ messages,
762
+ args.importFrom || ''
763
+ );
764
+ }
765
+
766
+ function readLockFile() {
767
+ const lockPath = path.resolve('protos.lock.json');
768
+ if (!fs.existsSync(lockPath)) {
769
+ fail('protos.lock.json not found in current directory');
770
+ }
771
+ const raw = JSON.parse(fs.readFileSync(lockPath, 'utf8'));
772
+ // Keys starting with "_" are documentation-only (JSON has no // comments).
773
+ const lock = {};
774
+ const keys = Object.keys(raw);
775
+ for (let i = 0; i < keys.length; i += 1) {
776
+ const key = keys[i];
777
+ if (!key || key.startsWith('_')) {
778
+ continue;
779
+ }
780
+ lock[key] = raw[key];
781
+ }
782
+ return lock;
783
+ }
784
+
785
+ function argsFromLock(lock) {
786
+ const defaults = emptyArgs();
787
+ defaults.command = 'generate';
788
+ defaults.out = lock.out || './packages/react-native-proto/src/generated';
789
+ defaults.importFrom = lock.importFrom || lock['import-from'] || '';
790
+
791
+ if (lock.source === 'local') {
792
+ if (!lock.path) {
793
+ fail('protos.lock.json: "path" is required when source is "local"');
794
+ }
795
+ defaults.from = 'local';
796
+ defaults.path = lock.path;
797
+ return defaults;
798
+ }
799
+
800
+ if (lock.source === 'github') {
801
+ if (!lock.repo || !lock.ref) {
802
+ fail(
803
+ 'protos.lock.json: "repo" and "ref" are required when source is "github"'
804
+ );
805
+ }
806
+ defaults.from = 'github';
807
+ defaults.repo = lock.repo;
808
+ defaults.ref = lock.ref;
809
+ return defaults;
810
+ }
811
+
812
+ if (lock.source === 'buf') {
813
+ if (!lock.module) {
814
+ fail('protos.lock.json: "module" is required when source is "buf"');
815
+ }
816
+ defaults.from = 'buf';
817
+ defaults.module = lock.module;
818
+ defaults.ref = lock.ref || '';
819
+ return defaults;
820
+ }
821
+
822
+ fail('protos.lock.json source must be "local", "github", or "buf"');
823
+ return defaults;
824
+ }
825
+
826
+ function syncFromLock() {
827
+ const lock = readLockFile();
828
+ generate(argsFromLock(lock));
829
+ }
830
+
831
+ function resolveWatchArgs(args) {
832
+ const hasGenerateFlags =
833
+ Boolean(args.from) ||
834
+ Boolean(args.path) ||
835
+ Boolean(args.repo) ||
836
+ Boolean(args.module);
837
+
838
+ if (!hasGenerateFlags) {
839
+ return argsFromLock(readLockFile());
840
+ }
841
+
842
+ if (!args.from) {
843
+ fail('watch: --from is required when not using protos.lock.json');
844
+ }
845
+ return args;
846
+ }
847
+
848
+ function watchProtos(cliArgs) {
849
+ const args = resolveWatchArgs(cliArgs);
850
+
851
+ if (args.from !== 'local') {
852
+ fail(
853
+ 'watch only supports --from local (or a protos.lock.json with source "local"). Use generate/sync for github/buf.'
854
+ );
855
+ }
856
+ if (!args.path) {
857
+ fail('watch: --path is required for local proto watching');
858
+ }
859
+
860
+ const watchDir = path.resolve(args.path);
861
+ if (!fs.existsSync(watchDir)) {
862
+ fail(`Proto path does not exist: ${watchDir}`);
863
+ }
864
+
865
+ console.log(`react-native-proto: watching ${watchDir}`);
866
+ console.log(
867
+ `react-native-proto: debounce ${WATCH_DEBOUNCE_MS}ms — press Ctrl+C to stop`
868
+ );
869
+
870
+ const runGenerate = (reason) => {
871
+ console.log(`react-native-proto: ${reason}`);
872
+ try {
873
+ generate(args);
874
+ } catch (err) {
875
+ if (err instanceof CliError) {
876
+ console.error(`react-native-proto: ${err.message}`);
877
+ } else {
878
+ console.error('react-native-proto: generate failed', err);
879
+ }
880
+ }
881
+ };
882
+
883
+ runGenerate('initial generate');
884
+
885
+ let timer = null;
886
+ const onChange = (eventType, filename) => {
887
+ if (filename && !String(filename).endsWith('.proto')) {
888
+ return;
889
+ }
890
+ if (timer) clearTimeout(timer);
891
+ timer = setTimeout(() => {
892
+ const label = filename
893
+ ? `change detected (${filename}), regenerating…`
894
+ : `change detected (${eventType || 'event'}), regenerating…`;
895
+ runGenerate(label);
896
+ }, WATCH_DEBOUNCE_MS);
897
+ };
898
+
899
+ let watcher;
900
+ try {
901
+ watcher = fs.watch(watchDir, { recursive: true }, onChange);
902
+ } catch (_err) {
903
+ console.warn(
904
+ 'react-native-proto: recursive fs.watch not supported; watching top-level directory only'
905
+ );
906
+ watcher = fs.watch(watchDir, onChange);
907
+ }
908
+
909
+ watcher.on('error', (err) => {
910
+ console.error(`react-native-proto: watch error: ${err.message}`);
911
+ if (timer) clearTimeout(timer);
912
+ try {
913
+ watcher.close();
914
+ } catch (_closeErr) {
915
+ // ignore
916
+ }
917
+ process.exit(1);
918
+ });
919
+
920
+ const shutdown = () => {
921
+ console.log('\nreact-native-proto: stopped watching (SIGINT)');
922
+ if (timer) clearTimeout(timer);
923
+ try {
924
+ watcher.close();
925
+ } catch (_err) {
926
+ // ignore
927
+ }
928
+ process.exit(0);
929
+ };
930
+
931
+ process.on('SIGINT', shutdown);
932
+ process.on('SIGTERM', shutdown);
933
+ }
934
+
935
+ function main() {
936
+ try {
937
+ const args = parseArgs(process.argv.slice(2));
938
+ if (args.command === 'generate') {
939
+ generate(args);
940
+ } else if (args.command === 'sync') {
941
+ syncFromLock();
942
+ } else if (args.command === 'watch') {
943
+ watchProtos(args);
944
+ } else {
945
+ fail(`Unknown command: ${args.command}`);
946
+ }
947
+ } catch (err) {
948
+ if (err instanceof CliError) {
949
+ console.error(`react-native-proto: ${err.message}`);
950
+ process.exit(err.code);
951
+ }
952
+ throw err;
953
+ }
954
+ }
955
+
956
+ main();