goscript 0.2.0 → 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 (75) hide show
  1. package/cmd/goscript-wasm/main.go +38 -6
  2. package/compiler/diagnostic.go +104 -12
  3. package/compiler/diagnostic_test.go +106 -0
  4. package/compiler/gotest/runner.go +99 -17
  5. package/compiler/gotest/runner_test.go +65 -0
  6. package/compiler/index.test.ts +23 -0
  7. package/compiler/lowered-program.go +9 -7
  8. package/compiler/lowering.go +361 -72
  9. package/compiler/lowering_bench_test.go +1 -0
  10. package/compiler/lowering_internal_test.go +18 -0
  11. package/compiler/protobuf-ts-binding.go +65 -12
  12. package/compiler/protobuf-ts-binding_test.go +339 -0
  13. package/compiler/runtime-contract.go +4 -0
  14. package/compiler/runtime-contract_test.go +2 -0
  15. package/compiler/service.go +1 -0
  16. package/compiler/skeleton_test.go +60 -3
  17. package/compiler/wasm/compile_test.go +37 -4
  18. package/compiler/wasm-api.go +57 -7
  19. package/dist/gs/builtin/hostio.js +6 -1
  20. package/dist/gs/builtin/hostio.js.map +1 -1
  21. package/dist/gs/builtin/slice.d.ts +11 -1
  22. package/dist/gs/builtin/slice.js +158 -2
  23. package/dist/gs/builtin/slice.js.map +1 -1
  24. package/dist/gs/crypto/aes/index.d.ts +15 -0
  25. package/dist/gs/crypto/aes/index.js +57 -0
  26. package/dist/gs/crypto/aes/index.js.map +1 -0
  27. package/dist/gs/crypto/cipher/index.d.ts +41 -0
  28. package/dist/gs/crypto/cipher/index.js +255 -0
  29. package/dist/gs/crypto/cipher/index.js.map +1 -0
  30. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.d.ts +1 -0
  31. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js +30 -5
  32. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/index.js.map +1 -1
  33. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.d.ts +1 -0
  34. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.js +17 -11
  35. package/dist/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.js.map +1 -1
  36. package/dist/gs/golang.org/x/crypto/chacha20poly1305/index.d.ts +31 -0
  37. package/dist/gs/golang.org/x/crypto/chacha20poly1305/index.js +117 -0
  38. package/dist/gs/golang.org/x/crypto/chacha20poly1305/index.js.map +1 -0
  39. package/dist/gs/internal/byteorder/index.js +2 -2
  40. package/dist/gs/internal/byteorder/index.js.map +1 -1
  41. package/dist/gs/io/io.js +18 -2
  42. package/dist/gs/io/io.js.map +1 -1
  43. package/dist/gs/reflect/type.js +57 -0
  44. package/dist/gs/reflect/type.js.map +1 -1
  45. package/dist/gs/runtime/debug/index.js +2 -1
  46. package/dist/gs/runtime/debug/index.js.map +1 -1
  47. package/dist/gs/sync/atomic/doc_64.gs.js +7 -6
  48. package/dist/gs/sync/atomic/doc_64.gs.js.map +1 -1
  49. package/go.mod +2 -2
  50. package/go.sum +2 -0
  51. package/gs/builtin/hostio.test.ts +22 -1
  52. package/gs/builtin/hostio.ts +6 -1
  53. package/gs/builtin/runtime-contract.test.ts +28 -0
  54. package/gs/builtin/slice.ts +225 -20
  55. package/gs/crypto/aes/index.test.ts +120 -0
  56. package/gs/crypto/aes/index.ts +76 -0
  57. package/gs/crypto/cipher/index.ts +345 -0
  58. package/gs/crypto/cipher/meta.json +6 -0
  59. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.test.ts +162 -0
  60. package/gs/github.com/aperturerobotics/protobuf-go-lite/index.ts +41 -5
  61. package/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.test.ts +18 -0
  62. package/gs/github.com/aperturerobotics/protobuf-go-lite/json/index.ts +17 -11
  63. package/gs/golang.org/x/crypto/chacha20poly1305/index.test.ts +91 -0
  64. package/gs/golang.org/x/crypto/chacha20poly1305/index.ts +245 -0
  65. package/gs/internal/byteorder/index.test.ts +2 -2
  66. package/gs/internal/byteorder/index.ts +2 -2
  67. package/gs/io/io.test.ts +56 -1
  68. package/gs/io/io.ts +19 -2
  69. package/gs/reflect/type.ts +64 -0
  70. package/gs/reflect/typefor.test.ts +21 -1
  71. package/gs/runtime/debug/index.test.ts +32 -4
  72. package/gs/runtime/debug/index.ts +5 -2
  73. package/gs/sync/atomic/doc_64.gs.ts +6 -7
  74. package/gs/sync/atomic/doc_64.test.ts +43 -0
  75. package/package.json +10 -3
@@ -0,0 +1,76 @@
1
+ import * as $ from '@goscript/builtin/index.js'
2
+ import type * as cipher from '@goscript/crypto/cipher/index.js'
3
+ import { ecb } from '@noble/ciphers/aes.js'
4
+
5
+ export const BlockSize = 16
6
+
7
+ export type KeySizeError = number
8
+
9
+ export class AESBlock implements cipher.Block {
10
+ private keyPromise: Promise<CryptoKey> | null = null
11
+
12
+ constructor(private readonly key: Uint8Array) {}
13
+
14
+ BlockSize(): number {
15
+ return BlockSize
16
+ }
17
+
18
+ Encrypt(dst: $.Bytes, src: $.Bytes): void {
19
+ const srcBytes = $.bytesToUint8Array(src)
20
+ if (srcBytes.length < BlockSize) {
21
+ $.panic('crypto/aes: input not full block')
22
+ }
23
+ if ($.len(dst) < BlockSize) {
24
+ $.panic('crypto/aes: output not full block')
25
+ }
26
+ const out = ecb(this.key, { disablePadding: true }).encrypt(
27
+ srcBytes.subarray(0, BlockSize),
28
+ )
29
+ $.copy(dst as Uint8Array, out)
30
+ }
31
+
32
+ Decrypt(dst: $.Bytes, src: $.Bytes): void {
33
+ const srcBytes = $.bytesToUint8Array(src)
34
+ if (srcBytes.length < BlockSize) {
35
+ $.panic('crypto/aes: input not full block')
36
+ }
37
+ if ($.len(dst) < BlockSize) {
38
+ $.panic('crypto/aes: output not full block')
39
+ }
40
+ const out = ecb(this.key, { disablePadding: true }).decrypt(
41
+ srcBytes.subarray(0, BlockSize),
42
+ )
43
+ $.copy(dst as Uint8Array, out)
44
+ }
45
+
46
+ async webCryptoKey(): Promise<CryptoKey> {
47
+ this.keyPromise ??= subtleCrypto().importKey(
48
+ 'raw',
49
+ this.key as unknown as BufferSource,
50
+ 'AES-GCM',
51
+ false,
52
+ ['encrypt', 'decrypt'],
53
+ )
54
+ return this.keyPromise
55
+ }
56
+ }
57
+
58
+ export function KeySizeError_Error(k: KeySizeError): string {
59
+ return `crypto/aes: invalid key size ${k}`
60
+ }
61
+
62
+ export function NewCipher(key: $.Bytes): [cipher.Block | null, $.GoError] {
63
+ const k = $.len(key)
64
+ if (k !== 16 && k !== 24 && k !== 32) {
65
+ return [null, $.newError(KeySizeError_Error(k))]
66
+ }
67
+ return [new AESBlock($.bytesToUint8Array(key).slice()), null]
68
+ }
69
+
70
+ function subtleCrypto(): SubtleCrypto {
71
+ const subtle = globalThis.crypto?.subtle
72
+ if (subtle == null) {
73
+ throw new Error('crypto/aes: WebCrypto AES-GCM is unavailable')
74
+ }
75
+ return subtle
76
+ }
@@ -0,0 +1,345 @@
1
+ import * as $ from '@goscript/builtin/index.js'
2
+
3
+ export type Block = {
4
+ BlockSize(): number
5
+ Decrypt(dst: $.Bytes, src: $.Bytes): void
6
+ Encrypt(dst: $.Bytes, src: $.Bytes): void
7
+ }
8
+
9
+ $.registerInterfaceType('cipher.Block', null, [
10
+ {
11
+ name: 'BlockSize',
12
+ args: [],
13
+ returns: [{ name: '_r0', type: { kind: $.TypeKind.Basic, name: 'int' } }],
14
+ },
15
+ {
16
+ name: 'Decrypt',
17
+ args: [
18
+ {
19
+ name: 'dst',
20
+ type: {
21
+ kind: $.TypeKind.Slice,
22
+ elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
23
+ },
24
+ },
25
+ {
26
+ name: 'src',
27
+ type: {
28
+ kind: $.TypeKind.Slice,
29
+ elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
30
+ },
31
+ },
32
+ ],
33
+ returns: [],
34
+ },
35
+ {
36
+ name: 'Encrypt',
37
+ args: [
38
+ {
39
+ name: 'dst',
40
+ type: {
41
+ kind: $.TypeKind.Slice,
42
+ elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
43
+ },
44
+ },
45
+ {
46
+ name: 'src',
47
+ type: {
48
+ kind: $.TypeKind.Slice,
49
+ elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
50
+ },
51
+ },
52
+ ],
53
+ returns: [],
54
+ },
55
+ ])
56
+
57
+ export type AEAD = {
58
+ NonceSize(): number
59
+ Open(
60
+ dst: $.Bytes,
61
+ nonce: $.Bytes,
62
+ ciphertext: $.Bytes,
63
+ additionalData: $.Bytes,
64
+ ): [$.Bytes, $.GoError] | Promise<[$.Bytes, $.GoError]>
65
+ Overhead(): number
66
+ Seal(
67
+ dst: $.Bytes,
68
+ nonce: $.Bytes,
69
+ plaintext: $.Bytes,
70
+ additionalData: $.Bytes,
71
+ ): $.Bytes | Promise<$.Bytes>
72
+ }
73
+
74
+ $.registerInterfaceType('cipher.AEAD', null, [
75
+ {
76
+ name: 'NonceSize',
77
+ args: [],
78
+ returns: [{ name: '_r0', type: { kind: $.TypeKind.Basic, name: 'int' } }],
79
+ },
80
+ {
81
+ name: 'Open',
82
+ args: [
83
+ {
84
+ name: 'dst',
85
+ type: {
86
+ kind: $.TypeKind.Slice,
87
+ elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
88
+ },
89
+ },
90
+ {
91
+ name: 'nonce',
92
+ type: {
93
+ kind: $.TypeKind.Slice,
94
+ elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
95
+ },
96
+ },
97
+ {
98
+ name: 'ciphertext',
99
+ type: {
100
+ kind: $.TypeKind.Slice,
101
+ elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
102
+ },
103
+ },
104
+ {
105
+ name: 'additionalData',
106
+ type: {
107
+ kind: $.TypeKind.Slice,
108
+ elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
109
+ },
110
+ },
111
+ ],
112
+ returns: [
113
+ {
114
+ name: '_r0',
115
+ type: {
116
+ kind: $.TypeKind.Slice,
117
+ elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
118
+ },
119
+ },
120
+ { name: '_r1', type: 'error' },
121
+ ],
122
+ },
123
+ {
124
+ name: 'Overhead',
125
+ args: [],
126
+ returns: [{ name: '_r0', type: { kind: $.TypeKind.Basic, name: 'int' } }],
127
+ },
128
+ {
129
+ name: 'Seal',
130
+ args: [
131
+ {
132
+ name: 'dst',
133
+ type: {
134
+ kind: $.TypeKind.Slice,
135
+ elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
136
+ },
137
+ },
138
+ {
139
+ name: 'nonce',
140
+ type: {
141
+ kind: $.TypeKind.Slice,
142
+ elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
143
+ },
144
+ },
145
+ {
146
+ name: 'plaintext',
147
+ type: {
148
+ kind: $.TypeKind.Slice,
149
+ elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
150
+ },
151
+ },
152
+ {
153
+ name: 'additionalData',
154
+ type: {
155
+ kind: $.TypeKind.Slice,
156
+ elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
157
+ },
158
+ },
159
+ ],
160
+ returns: [
161
+ {
162
+ name: '_r0',
163
+ type: {
164
+ kind: $.TypeKind.Slice,
165
+ elemType: { kind: $.TypeKind.Basic, name: 'uint8' },
166
+ },
167
+ },
168
+ ],
169
+ },
170
+ ])
171
+
172
+ export type Stream = {
173
+ XORKeyStream(dst: $.Bytes, src: $.Bytes): void
174
+ }
175
+
176
+ export type BlockMode = {
177
+ BlockSize(): number
178
+ CryptBlocks(dst: $.Bytes, src: $.Bytes): void
179
+ }
180
+
181
+ type WebCryptoBlock = Block & {
182
+ webCryptoKey(): Promise<CryptoKey>
183
+ }
184
+
185
+ class webCryptoGCM implements AEAD {
186
+ constructor(
187
+ private readonly block: WebCryptoBlock,
188
+ private readonly nonceSize: number,
189
+ private readonly tagSize: number,
190
+ ) {}
191
+
192
+ NonceSize(): number {
193
+ return this.nonceSize
194
+ }
195
+
196
+ Overhead(): number {
197
+ return this.tagSize
198
+ }
199
+
200
+ async Seal(
201
+ dst: $.Bytes,
202
+ nonce: $.Bytes,
203
+ plaintext: $.Bytes,
204
+ additionalData: $.Bytes,
205
+ ): Promise<$.Bytes> {
206
+ if ($.len(nonce) !== this.nonceSize) {
207
+ throw new Error('crypto/cipher: incorrect nonce length given to GCM')
208
+ }
209
+ const encrypted = await globalThis.crypto.subtle.encrypt(
210
+ this.algorithm(nonce, additionalData),
211
+ await this.block.webCryptoKey(),
212
+ $.bytesToUint8Array(plaintext) as unknown as BufferSource,
213
+ )
214
+ return appendBytes(dst, new Uint8Array(encrypted))
215
+ }
216
+
217
+ async Open(
218
+ dst: $.Bytes,
219
+ nonce: $.Bytes,
220
+ ciphertext: $.Bytes,
221
+ additionalData: $.Bytes,
222
+ ): Promise<[$.Bytes, $.GoError]> {
223
+ if ($.len(nonce) !== this.nonceSize) {
224
+ throw new Error('crypto/cipher: incorrect nonce length given to GCM')
225
+ }
226
+ try {
227
+ const decrypted = await globalThis.crypto.subtle.decrypt(
228
+ this.algorithm(nonce, additionalData),
229
+ await this.block.webCryptoKey(),
230
+ $.bytesToUint8Array(ciphertext) as unknown as BufferSource,
231
+ )
232
+ return [appendBytes(dst, new Uint8Array(decrypted)), null]
233
+ } catch {
234
+ return [null, $.newError('cipher: message authentication failed')]
235
+ }
236
+ }
237
+
238
+ private algorithm(nonce: $.Bytes, additionalData: $.Bytes): AesGcmParams {
239
+ const params: AesGcmParams = {
240
+ name: 'AES-GCM',
241
+ iv: $.bytesToUint8Array(nonce) as unknown as BufferSource,
242
+ tagLength: this.tagSize * 8,
243
+ }
244
+ if ($.len(additionalData) !== 0) {
245
+ params.additionalData = $.bytesToUint8Array(
246
+ additionalData,
247
+ ) as unknown as BufferSource
248
+ }
249
+ return params
250
+ }
251
+ }
252
+
253
+ export function NewGCM(block: Block | null): [AEAD | null, $.GoError] {
254
+ return NewGCMWithNonceSize(block, 12)
255
+ }
256
+
257
+ export function NewGCMWithNonceSize(
258
+ block: Block | null,
259
+ size: number,
260
+ ): [AEAD | null, $.GoError] {
261
+ return newGCM(block, size, 16)
262
+ }
263
+
264
+ export function NewGCMWithTagSize(
265
+ block: Block | null,
266
+ tagSize: number,
267
+ ): [AEAD | null, $.GoError] {
268
+ if (tagSize < 12 || tagSize > 16) {
269
+ return [null, $.newError('crypto/cipher: incorrect GCM tag size')]
270
+ }
271
+ return newGCM(block, 12, tagSize)
272
+ }
273
+
274
+ export function NewGCMWithRandomNonce(
275
+ _block: Block | null,
276
+ ): [AEAD | null, $.GoError] {
277
+ return [
278
+ null,
279
+ $.newError(
280
+ 'crypto/cipher: NewGCMWithRandomNonce is not implemented in GoScript',
281
+ ),
282
+ ]
283
+ }
284
+
285
+ export function NewCBCDecrypter(_b: Block | null, _iv: $.Bytes): BlockMode {
286
+ throw new Error('crypto/cipher: CBC is not implemented in GoScript')
287
+ }
288
+
289
+ export function NewCBCEncrypter(_b: Block | null, _iv: $.Bytes): BlockMode {
290
+ throw new Error('crypto/cipher: CBC is not implemented in GoScript')
291
+ }
292
+
293
+ export function NewCFBDecrypter(_b: Block | null, _iv: $.Bytes): Stream {
294
+ throw new Error('crypto/cipher: CFB is not implemented in GoScript')
295
+ }
296
+
297
+ export function NewCFBEncrypter(_b: Block | null, _iv: $.Bytes): Stream {
298
+ throw new Error('crypto/cipher: CFB is not implemented in GoScript')
299
+ }
300
+
301
+ export function NewCTR(_b: Block | null, _iv: $.Bytes): Stream {
302
+ throw new Error('crypto/cipher: CTR is not implemented in GoScript')
303
+ }
304
+
305
+ export function NewOFB(_b: Block | null, _iv: $.Bytes): Stream {
306
+ throw new Error('crypto/cipher: OFB is not implemented in GoScript')
307
+ }
308
+
309
+ export class StreamReader {
310
+ constructor(_init?: Partial<{ S: Stream; R: unknown }>) {}
311
+ }
312
+
313
+ export class StreamWriter {
314
+ constructor(_init?: Partial<{ S: Stream; W: unknown }>) {}
315
+ }
316
+
317
+ function newGCM(
318
+ block: Block | null,
319
+ nonceSize: number,
320
+ tagSize: number,
321
+ ): [AEAD | null, $.GoError] {
322
+ if (block == null || block.BlockSize() !== 16) {
323
+ return [null, $.newError('cipher: NewGCM requires 128-bit block cipher')]
324
+ }
325
+ if (nonceSize <= 0) {
326
+ return [null, $.newError('crypto/cipher: incorrect GCM nonce size')]
327
+ }
328
+ if (!isWebCryptoBlock(block)) {
329
+ return [
330
+ null,
331
+ $.newError(
332
+ 'crypto/cipher: AES-GCM requires a WebCrypto AES block in GoScript',
333
+ ),
334
+ ]
335
+ }
336
+ return [new webCryptoGCM(block, nonceSize, tagSize), null]
337
+ }
338
+
339
+ function isWebCryptoBlock(block: Block): block is WebCryptoBlock {
340
+ return typeof (block as Partial<WebCryptoBlock>).webCryptoKey === 'function'
341
+ }
342
+
343
+ function appendBytes(dst: $.Bytes, bytes: Uint8Array): $.Bytes {
344
+ return $.append(dst as any, ...bytes) as $.Bytes
345
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "asyncMethods": {
3
+ "AEAD.Open": true,
4
+ "AEAD.Seal": true
5
+ }
6
+ }
@@ -38,6 +38,8 @@ import {
38
38
  EqualVTSliceImplicit,
39
39
  type EqualVT,
40
40
  IsEqualVTSlice,
41
+ MarshalBoundMessageVT,
42
+ MarshalBoundMessageToSizedBufferVT,
41
43
  SizeBoolNonZero,
42
44
  SizeBoolPacked,
43
45
  SizeBoolPtr,
@@ -76,6 +78,7 @@ import {
76
78
  SizeZigzagSlice,
77
79
  SizeZigzagValue,
78
80
  Skip,
81
+ UnmarshalBoundMessageVT,
79
82
  } from './index.js'
80
83
 
81
84
  class TestValue {
@@ -146,6 +149,115 @@ class TestCloneMessage implements CloneMessage {
146
149
  }
147
150
  }
148
151
 
152
+ class BrokenBoundMessage {}
153
+
154
+ ;(BrokenBoundMessage as any).__protobufTypeScriptMessage = {
155
+ typeName: 'test.BrokenBoundMessage',
156
+ fields: { list: () => [] },
157
+ fromBinary: () => ({}),
158
+ toBinary: () => {
159
+ throw new Error('invalid uint 32: undefined')
160
+ },
161
+ }
162
+
163
+ class BytesBoundMessage {
164
+ Config: $.Slice<number>
165
+
166
+ constructor(config: $.Slice<number>) {
167
+ this.Config = config
168
+ }
169
+ }
170
+
171
+ ;(BytesBoundMessage as any).__protobufTypeScriptMessage = {
172
+ typeName: 'test.BytesBoundMessage',
173
+ fields: {
174
+ list: () => [{ localName: 'config', kind: 'scalar', T: 12 }],
175
+ },
176
+ fromBinary: () => ({}),
177
+ toBinary: (value: { config?: Uint8Array }) => {
178
+ expect(value.config).toBeInstanceOf(Uint8Array)
179
+ expect(Array.from(value.config ?? [])).toEqual([1, 2, 3])
180
+ return new Uint8Array([9])
181
+ },
182
+ }
183
+
184
+ class TimestampBoundMessage {
185
+ public get Seconds(): number {
186
+ return this._fields.Seconds.value
187
+ }
188
+ public set Seconds(value: number) {
189
+ this._fields.Seconds.value = value
190
+ }
191
+
192
+ public get Nanos(): number {
193
+ return this._fields.Nanos.value
194
+ }
195
+ public set Nanos(value: number) {
196
+ this._fields.Nanos.value = value
197
+ }
198
+
199
+ public _fields: {
200
+ Seconds: $.VarRef<number>
201
+ Nanos: $.VarRef<number>
202
+ }
203
+
204
+ constructor(init?: Partial<{ Seconds?: number; Nanos?: number }>) {
205
+ this._fields = {
206
+ Seconds: $.varRef(init?.Seconds ?? 0),
207
+ Nanos: $.varRef(init?.Nanos ?? 0),
208
+ }
209
+ }
210
+ }
211
+
212
+ const timestampMessageType = {
213
+ typeName: 'google.protobuf.Timestamp',
214
+ fields: {
215
+ list: () => [
216
+ { localName: 'seconds', kind: 'scalar' },
217
+ { localName: 'nanos', kind: 'scalar' },
218
+ ],
219
+ },
220
+ }
221
+
222
+ class TimestampParentBoundMessage {
223
+ public get Timestamp(): TimestampBoundMessage | null {
224
+ return this._fields.Timestamp.value
225
+ }
226
+ public set Timestamp(value: TimestampBoundMessage | null) {
227
+ this._fields.Timestamp.value = value
228
+ }
229
+
230
+ public _fields: {
231
+ Timestamp: $.VarRef<TimestampBoundMessage | null>
232
+ }
233
+
234
+ constructor(init?: Partial<{ Timestamp?: TimestampBoundMessage | null }>) {
235
+ this._fields = {
236
+ Timestamp: $.varRef(init?.Timestamp ?? null),
237
+ }
238
+ }
239
+ }
240
+
241
+ ;(TimestampParentBoundMessage as any).__protobufTypeScriptMessage = {
242
+ typeName: 'test.TimestampParentBoundMessage',
243
+ fields: {
244
+ list: () => [
245
+ {
246
+ localName: 'timestamp',
247
+ kind: 'message',
248
+ T: timestampMessageType,
249
+ },
250
+ ],
251
+ },
252
+ fromBinary: () => ({
253
+ timestamp: new Date(Date.UTC(2026, 4, 31, 12, 34, 56, 789)),
254
+ }),
255
+ toBinary: () => new Uint8Array(),
256
+ }
257
+ ;(TimestampParentBoundMessage as any).__protobufTypeScriptFields = {
258
+ timestamp: TimestampBoundMessage,
259
+ }
260
+
149
261
  describe('protobuf-go-lite EqualVT helpers', () => {
150
262
  it('accepts compiler-emitted runtime type arguments', () => {
151
263
  const equal = CompareEqualVT<TestValue>({
@@ -245,6 +357,56 @@ describe('protobuf-go-lite runtime interfaces', () => {
245
357
  })
246
358
  })
247
359
 
360
+ describe('protobuf-go-lite TypeScript binding helpers', () => {
361
+ it('adds message type context to binary marshal errors', () => {
362
+ const [, err] = MarshalBoundMessageVT(
363
+ BrokenBoundMessage as any,
364
+ new BrokenBoundMessage(),
365
+ )
366
+
367
+ expect(err?.Error()).toBe(
368
+ 'marshal test.BrokenBoundMessage: invalid uint 32: undefined',
369
+ )
370
+ })
371
+
372
+ it('normalizes Go byte slices before binary marshal', () => {
373
+ const [bytes, err] = MarshalBoundMessageVT(
374
+ BytesBoundMessage as any,
375
+ new BytesBoundMessage([1, 2, 3]),
376
+ )
377
+
378
+ expect(err).toBeNull()
379
+ expect(Array.from(bytes ?? [])).toEqual([9])
380
+ })
381
+
382
+ it('returns bytes written after marshaling into a sized buffer', () => {
383
+ const target = new Uint8Array([0, 0, 0])
384
+ const [n, err] = MarshalBoundMessageToSizedBufferVT(
385
+ BytesBoundMessage as any,
386
+ new BytesBoundMessage([1, 2, 3]),
387
+ target,
388
+ )
389
+
390
+ expect(err).toBeNull()
391
+ expect(n).toBe(1)
392
+ expect(Array.from(target)).toEqual([0, 0, 9])
393
+ })
394
+
395
+ it('hydrates protobuf-es-lite timestamp Date fields into Go timestamp structs', () => {
396
+ const target = new TimestampParentBoundMessage()
397
+
398
+ const err = UnmarshalBoundMessageVT(
399
+ TimestampParentBoundMessage as any,
400
+ target,
401
+ new Uint8Array(),
402
+ )
403
+
404
+ expect(err).toBeNull()
405
+ expect(target.Timestamp?.Seconds).toBe(1780230896)
406
+ expect(target.Timestamp?.Nanos).toBe(789000000)
407
+ })
408
+ })
409
+
248
410
  describe('protobuf-go-lite wire helpers', () => {
249
411
  it('encodes and decodes varints', () => {
250
412
  const buf = new Uint8Array(4)