@bigmistqke/rpc 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.
@@ -0,0 +1,351 @@
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest'
2
+ import { rpc, client, server, isStreamRequest } from '../src/stream/index'
3
+
4
+ const encoder = new TextEncoder()
5
+ const decoder = new TextDecoder()
6
+
7
+ // Helper to create a controllable ReadableStream
8
+ function createControllableStream() {
9
+ let controller: ReadableStreamDefaultController<Uint8Array>
10
+ const stream = new ReadableStream<Uint8Array>({
11
+ start(c) {
12
+ controller = c
13
+ },
14
+ })
15
+ return {
16
+ stream,
17
+ enqueue: (data: string) => controller.enqueue(encoder.encode(data)),
18
+ close: () => controller.close(),
19
+ }
20
+ }
21
+
22
+ // Helper to collect stream output
23
+ async function collectStream(stream: ReadableStream<Uint8Array>): Promise<string[]> {
24
+ const reader = stream.getReader()
25
+ const chunks: string[] = []
26
+ let buffer = ''
27
+
28
+ while (true) {
29
+ const { done, value } = await reader.read()
30
+ if (done) break
31
+ buffer += decoder.decode(value, { stream: true })
32
+
33
+ let newlineIndex
34
+ while ((newlineIndex = buffer.indexOf('\n')) !== -1) {
35
+ chunks.push(buffer.slice(0, newlineIndex))
36
+ buffer = buffer.slice(newlineIndex + 1)
37
+ }
38
+ }
39
+
40
+ return chunks
41
+ }
42
+
43
+ describe('isStreamRequest', () => {
44
+ it('should return true for requests with stream header', () => {
45
+ const request = new Request('http://example.com', {
46
+ headers: { RPC_STREAM_REQUEST_HEADER: '1' },
47
+ })
48
+ expect(isStreamRequest({ request })).toBe(true)
49
+ })
50
+
51
+ it('should return false for requests without stream header', () => {
52
+ const request = new Request('http://example.com')
53
+ expect(isStreamRequest({ request })).toBe(false)
54
+ })
55
+ })
56
+
57
+ describe('rpc', () => {
58
+ it('should create proxy and stream', () => {
59
+ const inputStream = new ReadableStream()
60
+ const { proxy, stream, closed, onClose } = rpc(inputStream, {})
61
+
62
+ expect(proxy).toBeDefined()
63
+ expect(stream).toBeInstanceOf(ReadableStream)
64
+ expect(typeof closed).toBe('function')
65
+ expect(typeof onClose).toBe('function')
66
+ })
67
+
68
+ it('should handle bidirectional communication', async () => {
69
+ // Create two connected RPC endpoints
70
+ const { stream: stream1, enqueue: enqueue1, close: close1 } = createControllableStream()
71
+ const { stream: stream2, enqueue: enqueue2, close: close2 } = createControllableStream()
72
+
73
+ const methods1 = {
74
+ ping: () => 'pong',
75
+ }
76
+
77
+ const methods2 = {
78
+ echo: (msg: string) => msg,
79
+ }
80
+
81
+ // Set up endpoint 1 - reads from stream1, writes to its output stream
82
+ const endpoint1 = rpc<{ echo: (msg: string) => string }, typeof methods1>(
83
+ stream1,
84
+ methods1,
85
+ )
86
+
87
+ // Set up endpoint 2 - reads from stream2, writes to its output stream
88
+ const endpoint2 = rpc<{ ping: () => string }, typeof methods2>(stream2, methods2)
89
+
90
+ // Connect the streams: endpoint1's output goes to endpoint2's input and vice versa
91
+ // This simulates the bidirectional connection
92
+ const reader1 = endpoint1.stream.getReader()
93
+ const reader2 = endpoint2.stream.getReader()
94
+
95
+ // Forward messages from endpoint1 to endpoint2
96
+ ;(async () => {
97
+ while (true) {
98
+ const { done, value } = await reader1.read()
99
+ if (done) break
100
+ enqueue2(decoder.decode(value))
101
+ }
102
+ })()
103
+
104
+ // Forward messages from endpoint2 to endpoint1
105
+ ;(async () => {
106
+ while (true) {
107
+ const { done, value } = await reader2.read()
108
+ if (done) break
109
+ enqueue1(decoder.decode(value))
110
+ }
111
+ })()
112
+
113
+ // Test RPC calls
114
+ const pingResult = await endpoint2.proxy.ping()
115
+ expect(pingResult).toBe('pong')
116
+
117
+ const echoResult = await endpoint1.proxy.echo('hello')
118
+ expect(echoResult).toBe('hello')
119
+ })
120
+
121
+ it('should serialize requests as JSON lines', async () => {
122
+ const inputStream = new ReadableStream()
123
+ const { proxy, stream } = rpc<{ test: () => void }>(inputStream, {})
124
+
125
+ // Get reader before making request
126
+ const reader = stream.getReader()
127
+ const decoder = new TextDecoder()
128
+
129
+ // Make a request (don't await - it will hang since there's no response)
130
+ proxy.test()
131
+
132
+ // Read the first chunk
133
+ const { value, done } = await reader.read()
134
+ expect(done).toBe(false)
135
+ expect(value).toBeInstanceOf(Uint8Array)
136
+
137
+ // Verify it's JSON line format (ends with newline)
138
+ const text = decoder.decode(value)
139
+ expect(text.endsWith('\n')).toBe(true)
140
+
141
+ // Should be valid JSON
142
+ const parsed = JSON.parse(text.trim())
143
+ expect(parsed).toHaveProperty('RPC_PROXY_REQUEST')
144
+ expect(parsed).toHaveProperty('payload')
145
+
146
+ reader.releaseLock()
147
+ })
148
+
149
+ it('should throw when stream is closed', async () => {
150
+ const { stream: inputStream, close } = createControllableStream()
151
+ const { proxy, closed, stream } = rpc<{ test: () => void }>(inputStream, {})
152
+
153
+ // Cancel the output stream to trigger closed state
154
+ const reader = stream.getReader()
155
+ await reader.cancel()
156
+
157
+ // Now the closed() should return true and proxy calls should throw
158
+ expect(closed()).toBe(true)
159
+ await expect(proxy.test()).rejects.toThrow('[rpc/sse] Stream is closed.')
160
+ })
161
+
162
+ it('should accept stream from function', async () => {
163
+ const inputStream = new ReadableStream()
164
+ const streamFactory = vi.fn((outputStream: ReadableStream) => inputStream)
165
+
166
+ const { proxy, stream } = rpc(streamFactory, {})
167
+
168
+ expect(streamFactory).toHaveBeenCalledWith(stream)
169
+ })
170
+
171
+ it('should accept stream from promise', async () => {
172
+ const inputStream = new ReadableStream()
173
+ const streamPromise = Promise.resolve(inputStream)
174
+
175
+ const { proxy } = rpc(streamPromise, {})
176
+
177
+ expect(proxy).toBeDefined()
178
+ })
179
+
180
+ it('should use custom codec when provided', async () => {
181
+ const { stream: inputStream, enqueue, close } = createControllableStream()
182
+
183
+ const customCodec = {
184
+ serialize: vi.fn((value: any, onChunk: (chunk: any) => void) => {
185
+ onChunk(encoder.encode(`CUSTOM:${JSON.stringify(value)}\n`))
186
+ }),
187
+ deserialize: vi.fn(async (stream: ReadableStream, onChunk: (chunk: any) => void) => {
188
+ for await (const chunk of stream as unknown as AsyncIterable<Uint8Array>) {
189
+ const text = decoder.decode(chunk)
190
+ const lines = text.split('\n').filter(Boolean)
191
+ for (const line of lines) {
192
+ if (line.startsWith('CUSTOM:')) {
193
+ onChunk(JSON.parse(line.slice(7)))
194
+ }
195
+ }
196
+ }
197
+ }),
198
+ }
199
+
200
+ const { proxy, stream } = rpc<{ test: () => void }>(inputStream, {}, customCodec)
201
+
202
+ proxy.test()
203
+
204
+ await new Promise(resolve => setTimeout(resolve, 10))
205
+
206
+ expect(customCodec.serialize).toHaveBeenCalled()
207
+ expect(customCodec.deserialize).toHaveBeenCalled()
208
+ })
209
+
210
+ it('should handle nested method calls', async () => {
211
+ const { stream: stream1, enqueue: enqueue1 } = createControllableStream()
212
+ const { stream: stream2, enqueue: enqueue2 } = createControllableStream()
213
+
214
+ const methods = {
215
+ user: {
216
+ profile: {
217
+ getName: () => 'John Doe',
218
+ },
219
+ },
220
+ }
221
+
222
+ const endpoint1 = rpc<typeof methods>(stream1, methods)
223
+ const endpoint2 = rpc<{}, typeof methods>(stream2, {})
224
+
225
+ // Wire up the streams
226
+ const reader1 = endpoint1.stream.getReader()
227
+ const reader2 = endpoint2.stream.getReader()
228
+
229
+ ;(async () => {
230
+ while (true) {
231
+ const { done, value } = await reader1.read()
232
+ if (done) break
233
+ enqueue2(decoder.decode(value))
234
+ }
235
+ })()
236
+
237
+ ;(async () => {
238
+ while (true) {
239
+ const { done, value } = await reader2.read()
240
+ if (done) break
241
+ enqueue1(decoder.decode(value))
242
+ }
243
+ })()
244
+
245
+ const result = await endpoint2.proxy.user.profile.getName()
246
+ expect(result).toBe('John Doe')
247
+ })
248
+ })
249
+
250
+ describe('server', () => {
251
+ it('should create a Response with correct headers', () => {
252
+ const inputStream = new ReadableStream()
253
+ const { response } = server(inputStream, {})
254
+
255
+ expect(response).toBeInstanceOf(Response)
256
+ expect(response.headers.get('Content-Type')).toBe('text/event-stream')
257
+ expect(response.headers.get('Cache-Control')).toBe('no-cache')
258
+ expect(response.headers.get('Connection')).toBe('keep-alive')
259
+ })
260
+
261
+ it('should expose proxy and lifecycle methods', () => {
262
+ const inputStream = new ReadableStream()
263
+ const { proxy, closed, onClose, response } = server(inputStream, {})
264
+
265
+ expect(proxy).toBeDefined()
266
+ expect(typeof closed).toBe('function')
267
+ expect(typeof onClose).toBe('function')
268
+ expect(response).toBeInstanceOf(Response)
269
+ })
270
+ })
271
+
272
+ describe('client', () => {
273
+ const originalFetch = globalThis.fetch
274
+
275
+ beforeEach(() => {
276
+ globalThis.fetch = vi.fn()
277
+ })
278
+
279
+ afterEach(() => {
280
+ globalThis.fetch = originalFetch
281
+ })
282
+
283
+ it('should make POST request with correct headers', async () => {
284
+ const mockResponseStream = new ReadableStream()
285
+ vi.mocked(globalThis.fetch).mockResolvedValue({
286
+ body: mockResponseStream,
287
+ } as Response)
288
+
289
+ const { proxy, stream } = client('http://example.com/rpc', {})
290
+
291
+ // Verify fetch was called with correct options
292
+ await new Promise(resolve => setTimeout(resolve, 10))
293
+
294
+ expect(globalThis.fetch).toHaveBeenCalledWith(
295
+ 'http://example.com/rpc',
296
+ expect.objectContaining({
297
+ method: 'POST',
298
+ duplex: 'half',
299
+ headers: expect.objectContaining({
300
+ 'Content-Type': 'text/event-stream',
301
+ 'Cache-Control': 'no-cache',
302
+ Connection: 'keep-alive',
303
+ RPC_STREAM_REQUEST_HEADER: '1',
304
+ }),
305
+ }),
306
+ )
307
+ })
308
+
309
+ it('should send output stream as request body', async () => {
310
+ const mockResponseStream = new ReadableStream()
311
+ vi.mocked(globalThis.fetch).mockResolvedValue({
312
+ body: mockResponseStream,
313
+ } as Response)
314
+
315
+ const { stream } = client('http://example.com/rpc', {})
316
+
317
+ await new Promise(resolve => setTimeout(resolve, 10))
318
+
319
+ const fetchCall = vi.mocked(globalThis.fetch).mock.calls[0]
320
+ expect(fetchCall[1]?.body).toBeInstanceOf(ReadableStream)
321
+ })
322
+ })
323
+
324
+ describe('onClose callback', () => {
325
+ it('should call onClose handlers when stream is cancelled', async () => {
326
+ const inputStream = new ReadableStream()
327
+ const { stream, onClose } = rpc(inputStream, {})
328
+
329
+ const handler = vi.fn()
330
+ onClose(handler)
331
+
332
+ const reader = stream.getReader()
333
+ await reader.cancel()
334
+
335
+ expect(handler).toHaveBeenCalled()
336
+ })
337
+
338
+ it('should allow unsubscribing from onClose', async () => {
339
+ const inputStream = new ReadableStream()
340
+ const { stream, onClose } = rpc(inputStream, {})
341
+
342
+ const handler = vi.fn()
343
+ const unsubscribe = onClose(handler)
344
+ unsubscribe()
345
+
346
+ const reader = stream.getReader()
347
+ await reader.cancel()
348
+
349
+ expect(handler).not.toHaveBeenCalled()
350
+ })
351
+ })
@@ -0,0 +1,336 @@
1
+ import { describe, it, expect, vi } from 'vitest'
2
+ import {
3
+ createIdAllocator,
4
+ createIdRegistry,
5
+ createPromiseRegistry,
6
+ defer,
7
+ createCommander,
8
+ callMethod,
9
+ createReadableStream,
10
+ streamToAsyncIterable,
11
+ createShape,
12
+ } from '../src/utils'
13
+ import * as v from 'valibot'
14
+
15
+ describe('createIdAllocator', () => {
16
+ it('should generate sequential IDs starting from 0', () => {
17
+ const allocator = createIdAllocator()
18
+ expect(allocator.create()).toBe(0)
19
+ expect(allocator.create()).toBe(1)
20
+ expect(allocator.create()).toBe(2)
21
+ })
22
+
23
+ it('should reuse freed IDs', () => {
24
+ const allocator = createIdAllocator()
25
+ const id0 = allocator.create()
26
+ const id1 = allocator.create()
27
+ const id2 = allocator.create()
28
+
29
+ allocator.free(id1)
30
+ expect(allocator.create()).toBe(1) // Reuses freed ID
31
+
32
+ allocator.free(id0)
33
+ allocator.free(id2)
34
+ expect(allocator.create()).toBe(2) // LIFO order for freed IDs
35
+ expect(allocator.create()).toBe(0)
36
+ })
37
+
38
+ it('should continue generating new IDs when no freed IDs available', () => {
39
+ const allocator = createIdAllocator()
40
+ allocator.create() // 0
41
+ allocator.create() // 1
42
+ allocator.free(0)
43
+ allocator.create() // reuses 0
44
+ expect(allocator.create()).toBe(2) // new ID
45
+ })
46
+ })
47
+
48
+ describe('createIdRegistry', () => {
49
+ it('should register values and return IDs', () => {
50
+ const registry = createIdRegistry<string>()
51
+ const id0 = registry.register('foo')
52
+ const id1 = registry.register('bar')
53
+ expect(id0).toBe(0)
54
+ expect(id1).toBe(1)
55
+ })
56
+
57
+ it('should free IDs and return stored values', () => {
58
+ const registry = createIdRegistry<string>()
59
+ registry.register('foo')
60
+ registry.register('bar')
61
+
62
+ expect(registry.free(0)).toBe('foo')
63
+ expect(registry.free(1)).toBe('bar')
64
+ })
65
+
66
+ it('should return undefined for non-existent IDs', () => {
67
+ const registry = createIdRegistry<string>()
68
+ expect(registry.free(999)).toBeUndefined()
69
+ })
70
+
71
+ it('should reuse freed IDs for new registrations', () => {
72
+ const registry = createIdRegistry<string>()
73
+ const id0 = registry.register('foo')
74
+ registry.free(id0)
75
+ const id1 = registry.register('bar')
76
+ expect(id1).toBe(0) // Reused
77
+ })
78
+ })
79
+
80
+ describe('createPromiseRegistry', () => {
81
+ it('should register promise handlers and retrieve them', () => {
82
+ const registry = createPromiseRegistry()
83
+ const resolve = vi.fn()
84
+ const reject = vi.fn()
85
+
86
+ const id = registry.register({ resolve, reject })
87
+ const handlers = registry.free(id)
88
+
89
+ expect(handlers).toEqual({ resolve, reject })
90
+ })
91
+ })
92
+
93
+ describe('defer', () => {
94
+ it('should create a deferred promise that can be resolved', async () => {
95
+ const deferred = defer<string>()
96
+ deferred.resolve('test')
97
+ await expect(deferred.promise).resolves.toBe('test')
98
+ })
99
+
100
+ it('should create a deferred promise that can be rejected', async () => {
101
+ const deferred = defer<string>()
102
+ deferred.reject(new Error('test error'))
103
+ await expect(deferred.promise).rejects.toThrow('test error')
104
+ })
105
+
106
+ it('should work with void type', async () => {
107
+ const deferred = defer()
108
+ deferred.resolve()
109
+ await expect(deferred.promise).resolves.toBeUndefined()
110
+ })
111
+ })
112
+
113
+ describe('createCommander', () => {
114
+ it('should track property access as topics', () => {
115
+ const apply = vi.fn()
116
+ const commander = createCommander<{ foo: { bar: () => void } }>(apply)
117
+
118
+ commander.foo.bar()
119
+
120
+ expect(apply).toHaveBeenCalledWith(['foo', 'bar'], [])
121
+ })
122
+
123
+ it('should pass arguments to apply function', () => {
124
+ const apply = vi.fn()
125
+ const commander = createCommander<{ method: (a: number, b: string) => void }>(apply)
126
+
127
+ commander.method(42, 'hello')
128
+
129
+ expect(apply).toHaveBeenCalledWith(['method'], [42, 'hello'])
130
+ })
131
+
132
+ it('should handle deeply nested paths', () => {
133
+ const apply = vi.fn()
134
+ const commander = createCommander<{ a: { b: { c: { d: () => void } } } }>(apply)
135
+
136
+ commander.a.b.c.d()
137
+
138
+ expect(apply).toHaveBeenCalledWith(['a', 'b', 'c', 'd'], [])
139
+ })
140
+
141
+ it('should return undefined for symbol properties', () => {
142
+ const apply = vi.fn()
143
+ const commander = createCommander(apply)
144
+
145
+ expect((commander as any)[Symbol.iterator]).toBeUndefined()
146
+ })
147
+
148
+ it('should return result from apply function', () => {
149
+ const apply = vi.fn().mockReturnValue('result')
150
+ const commander = createCommander<{ method: () => string }>(apply)
151
+
152
+ const result = commander.method()
153
+
154
+ expect(result).toBe('result')
155
+ })
156
+ })
157
+
158
+ describe('callMethod', () => {
159
+ it('should call top-level method', () => {
160
+ const methods = {
161
+ greet: (name: string) => `Hello, ${name}!`,
162
+ }
163
+
164
+ expect(callMethod(methods, ['greet'], ['World'])).toBe('Hello, World!')
165
+ })
166
+
167
+ it('should call nested method', () => {
168
+ const methods = {
169
+ user: {
170
+ profile: {
171
+ getName: () => 'John',
172
+ },
173
+ },
174
+ }
175
+
176
+ expect(callMethod(methods, ['user', 'profile', 'getName'], [])).toBe('John')
177
+ })
178
+
179
+ it('should pass multiple arguments', () => {
180
+ const methods = {
181
+ add: (a: number, b: number) => a + b,
182
+ }
183
+
184
+ expect(callMethod(methods, ['add'], [2, 3])).toBe(5)
185
+ })
186
+
187
+ it('should throw if topics do not resolve to a function', () => {
188
+ const methods = {
189
+ value: 42,
190
+ }
191
+
192
+ expect(() => callMethod(methods, ['value'], [])).toThrow(
193
+ 'Topics did not resolve to a function: [value]',
194
+ )
195
+ })
196
+
197
+ it('should throw if intermediate path is undefined', () => {
198
+ const methods = {
199
+ foo: {},
200
+ }
201
+
202
+ expect(() => callMethod(methods, ['foo', 'bar', 'baz'], [])).toThrow(
203
+ 'Topics did not resolve to a function: [foo,bar,baz]',
204
+ )
205
+ })
206
+
207
+ it('should throw for non-existent path', () => {
208
+ const methods = {}
209
+
210
+ expect(() => callMethod(methods, ['nonexistent'], [])).toThrow(
211
+ 'Topics did not resolve to a function: [nonexistent]',
212
+ )
213
+ })
214
+ })
215
+
216
+ describe('createShape', () => {
217
+ it('should validate values against schema', () => {
218
+ const shape = createShape(v.object({ name: v.string() }), (name: string) => ({ name }))
219
+
220
+ expect(shape.validate({ name: 'test' })).toBe(true)
221
+ expect(shape.validate({ name: 123 })).toBe(false)
222
+ expect(shape.validate({})).toBe(false)
223
+ expect(shape.validate(null)).toBe(false)
224
+ })
225
+
226
+ it('should create valid instances', () => {
227
+ const shape = createShape(
228
+ v.object({ id: v.number(), value: v.string() }),
229
+ (id: number, value: string) => ({ id, value }),
230
+ )
231
+
232
+ const instance = shape.create(1, 'test')
233
+ expect(instance).toEqual({ id: 1, value: 'test' })
234
+ expect(shape.validate(instance)).toBe(true)
235
+ })
236
+ })
237
+
238
+ describe('createReadableStream', () => {
239
+ it('should create a readable stream with controller', () => {
240
+ const { stream, controller } = createReadableStream()
241
+ expect(stream).toBeInstanceOf(ReadableStream)
242
+ expect(controller).toBeDefined()
243
+ })
244
+
245
+ it('should allow enqueueing data', async () => {
246
+ const { stream, enqueue } = createReadableStream()
247
+
248
+ enqueue('chunk1')
249
+ enqueue('chunk2')
250
+
251
+ const reader = stream.getReader()
252
+ expect(await reader.read()).toEqual({ value: 'chunk1', done: false })
253
+ expect(await reader.read()).toEqual({ value: 'chunk2', done: false })
254
+ })
255
+
256
+ it('should track closed state', () => {
257
+ const { closed } = createReadableStream()
258
+ expect(closed()).toBe(false)
259
+ })
260
+
261
+ it('should call onClose handlers when stream is cancelled', async () => {
262
+ const { stream, onClose } = createReadableStream()
263
+ const handler = vi.fn()
264
+
265
+ onClose(handler)
266
+
267
+ const reader = stream.getReader()
268
+ await reader.cancel()
269
+
270
+ expect(handler).toHaveBeenCalled()
271
+ })
272
+
273
+ it('should allow removing onClose handlers', async () => {
274
+ const { stream, onClose } = createReadableStream()
275
+ const handler = vi.fn()
276
+
277
+ const unsubscribe = onClose(handler)
278
+ unsubscribe()
279
+
280
+ const reader = stream.getReader()
281
+ await reader.cancel()
282
+
283
+ expect(handler).not.toHaveBeenCalled()
284
+ })
285
+ })
286
+
287
+ describe('streamToAsyncIterable', () => {
288
+ it('should convert ReadableStream to AsyncIterable', async () => {
289
+ const stream = new ReadableStream({
290
+ start(controller) {
291
+ controller.enqueue(1)
292
+ controller.enqueue(2)
293
+ controller.enqueue(3)
294
+ controller.close()
295
+ },
296
+ })
297
+
298
+ const iterable = streamToAsyncIterable(stream)
299
+ const results: number[] = []
300
+
301
+ for await (const value of iterable) {
302
+ results.push(value)
303
+ }
304
+
305
+ expect(results).toEqual([1, 2, 3])
306
+ })
307
+
308
+ it('should use native async iterator if available', async () => {
309
+ const mockStream = {
310
+ [Symbol.asyncIterator]: function* () {
311
+ yield 1
312
+ yield 2
313
+ },
314
+ } as unknown as ReadableStream<number>
315
+
316
+ const iterable = streamToAsyncIterable(mockStream)
317
+ expect(iterable).toBe(mockStream)
318
+ })
319
+
320
+ it('should handle empty stream', async () => {
321
+ const stream = new ReadableStream({
322
+ start(controller) {
323
+ controller.close()
324
+ },
325
+ })
326
+
327
+ const iterable = streamToAsyncIterable(stream)
328
+ const results: unknown[] = []
329
+
330
+ for await (const value of iterable) {
331
+ results.push(value)
332
+ }
333
+
334
+ expect(results).toEqual([])
335
+ })
336
+ })
package/tsconfig.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "compilerOptions": {
3
+ "strict": true,
4
+ "target": "ESNext",
5
+ "module": "ESNext",
6
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
7
+ "moduleResolution": "bundler",
8
+ "resolveJsonModule": true,
9
+ "esModuleInterop": true,
10
+ "noEmit": true,
11
+ "isolatedModules": true,
12
+ "skipLibCheck": true,
13
+ "allowSyntheticDefaultImports": true,
14
+ "forceConsistentCasingInFileNames": true,
15
+ "noUncheckedIndexedAccess": true,
16
+ "jsx": "preserve",
17
+ "jsxImportSource": "solid-js",
18
+ "paths": {
19
+ "src": ["./src"]
20
+ }
21
+ },
22
+ "exclude": ["./service.ts", "src/fetch/node.ts"]
23
+ }