spiceflow 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (81) hide show
  1. package/README.md +178 -0
  2. package/context.d.ts +2 -0
  3. package/context.js +1 -0
  4. package/dist/babel.test.d.ts +2 -0
  5. package/dist/babel.test.d.ts.map +1 -0
  6. package/dist/babel.test.js +32 -0
  7. package/dist/babel.test.js.map +1 -0
  8. package/dist/babelDebugOutputs.d.ts +9 -0
  9. package/dist/babelDebugOutputs.d.ts.map +1 -0
  10. package/dist/babelDebugOutputs.js +40 -0
  11. package/dist/babelDebugOutputs.js.map +1 -0
  12. package/dist/babelTransformRpc.d.ts +17 -0
  13. package/dist/babelTransformRpc.d.ts.map +1 -0
  14. package/dist/babelTransformRpc.js +304 -0
  15. package/dist/babelTransformRpc.js.map +1 -0
  16. package/dist/browser.d.ts +8 -0
  17. package/dist/browser.d.ts.map +1 -0
  18. package/dist/browser.js +133 -0
  19. package/dist/browser.js.map +1 -0
  20. package/dist/build.d.ts +10 -0
  21. package/dist/build.d.ts.map +1 -0
  22. package/dist/build.js +253 -0
  23. package/dist/build.js.map +1 -0
  24. package/dist/cli.d.ts +3 -0
  25. package/dist/cli.d.ts.map +1 -0
  26. package/dist/cli.js +108 -0
  27. package/dist/cli.js.map +1 -0
  28. package/dist/context-internal.d.ts +20 -0
  29. package/dist/context-internal.d.ts.map +1 -0
  30. package/dist/context-internal.js +22 -0
  31. package/dist/context-internal.js.map +1 -0
  32. package/dist/context.d.ts +2 -0
  33. package/dist/context.d.ts.map +1 -0
  34. package/dist/context.js +8 -0
  35. package/dist/context.js.map +1 -0
  36. package/dist/expose.d.ts +6 -0
  37. package/dist/expose.d.ts.map +1 -0
  38. package/dist/expose.js +39 -0
  39. package/dist/expose.js.map +1 -0
  40. package/dist/headers.d.ts +1 -0
  41. package/dist/headers.d.ts.map +1 -0
  42. package/dist/headers.js +18 -0
  43. package/dist/headers.js.map +1 -0
  44. package/dist/index.d.ts +8 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +55 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/jsonRpc.d.ts +32 -0
  49. package/dist/jsonRpc.d.ts.map +1 -0
  50. package/dist/jsonRpc.js +4 -0
  51. package/dist/jsonRpc.js.map +1 -0
  52. package/dist/server.d.ts +32 -0
  53. package/dist/server.d.ts.map +1 -0
  54. package/dist/server.js +301 -0
  55. package/dist/server.js.map +1 -0
  56. package/dist/utils.d.ts +20 -0
  57. package/dist/utils.d.ts.map +1 -0
  58. package/dist/utils.js +44 -0
  59. package/dist/utils.js.map +1 -0
  60. package/headers.d.ts +2 -0
  61. package/headers.js +1 -0
  62. package/package.json +56 -0
  63. package/sdk-template/package.json +22 -0
  64. package/sdk-template/src/index.ts +2 -0
  65. package/sdk-template/src/v1/example.ts +5 -0
  66. package/sdk-template/src/v1/generator.ts +12 -0
  67. package/sdk-template/tsconfig.json +16 -0
  68. package/src/babel.test.ts +35 -0
  69. package/src/babelDebugOutputs.ts +56 -0
  70. package/src/babelTransformRpc.ts +404 -0
  71. package/src/browser.ts +142 -0
  72. package/src/build.ts +303 -0
  73. package/src/cli.ts +118 -0
  74. package/src/context-internal.ts +36 -0
  75. package/src/context.ts +1 -0
  76. package/src/expose.ts +34 -0
  77. package/src/headers.ts +19 -0
  78. package/src/index.ts +42 -0
  79. package/src/jsonRpc.ts +43 -0
  80. package/src/server.ts +384 -0
  81. package/src/utils.ts +61 -0
package/package.json ADDED
@@ -0,0 +1,56 @@
1
+ {
2
+ "name": "spiceflow",
3
+ "version": "0.0.1",
4
+ "description": "If GraphQL, JSON-RPC and React server actions had a baby, it would be called spiceflow",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "repository": "https://github.com/remorses/spiceflow",
8
+ "bin": "dist/cli.js",
9
+ "files": [
10
+ "dist",
11
+ "src",
12
+ "sdk-template",
13
+ "esm",
14
+ "context.d.ts",
15
+ "context.js",
16
+ "headers.d.ts",
17
+ "headers.js"
18
+ ],
19
+ "keywords": [],
20
+ "author": "Tommaso De Rossi, morse <beats.by.morse@gmail.com>",
21
+ "license": "",
22
+ "peerDependencies": {
23
+ "next": ">=10"
24
+ },
25
+ "dependencies": {
26
+ "@babel/core": "^7.24.0",
27
+ "@babel/parser": "^7.24.0",
28
+ "@babel/plugin-syntax-jsx": "^7.23.3",
29
+ "@babel/plugin-syntax-typescript": "^7.23.3",
30
+ "@babel/plugin-transform-typescript": "^7.23.6",
31
+ "@manypkg/get-packages": "^2.2.1",
32
+ "@microsoft/api-extractor": "^7.43.1",
33
+ "@types/fs-extra": "^11.0.4",
34
+ "cac": "^6.7.14",
35
+ "chokidar": "^3.6.0",
36
+ "eventsource-parser": "^1.1.2",
37
+ "fast-glob": "^3.3.2",
38
+ "find-up": "^7.0.0",
39
+ "fs-extra": "^11.2.0",
40
+ "picocolors": "^1.0.0",
41
+ "superjson": "^1.13.3",
42
+ "ts-json-schema-generator": "^2.1.0"
43
+ },
44
+ "devDependencies": {
45
+ "@babel/generator": "^7.23.6",
46
+ "@babel/types": "^7.24.0",
47
+ "@types/babel__core": "^7.20.5",
48
+ "@types/node": "20.2.5",
49
+ "next": "14.2.1",
50
+ "webpack": "^5.88.2"
51
+ },
52
+ "scripts": {
53
+ "build": "cp ../README.md ./README.md && rm -rf ./sdk-template ../sdk-template/dist ../sdk-template/node_modules && cp -r ../sdk-template ./sdk-template && rm -rf dist && tsc",
54
+ "watch": "tsc -w"
55
+ }
56
+ }
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "sdk-template",
3
+ "version": "0.0.0",
4
+ "description": "Spiceflow API template",
5
+ "private": true,
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "type": "module",
9
+ "files": [
10
+ "dist",
11
+ "README.md"
12
+ ],
13
+ "scripts": {
14
+ "build": "spiceflow --url http://localhost:3333",
15
+ "watch": "spiceflow --watch --url http://localhost:3333",
16
+ "serve": "spiceflow serve --port 3333"
17
+ },
18
+ "dependencies": {
19
+ "spiceflow": "workspace:*"
20
+ },
21
+ "devDependencies": {}
22
+ }
@@ -0,0 +1,2 @@
1
+ export * from './v1/example';
2
+ export * from './v1/generator';
@@ -0,0 +1,5 @@
1
+ "use spiceflow"
2
+
3
+ export async function action({ }) {
4
+ return 'hello';
5
+ }
@@ -0,0 +1,12 @@
1
+ "use spiceflow"
2
+
3
+ export async function* generator() {
4
+ for (let i = 0; i < 10; i++) {
5
+ await sleep(300);
6
+ yield { i };
7
+ }
8
+ }
9
+
10
+ function sleep(ms: number) {
11
+ return new Promise((resolve) => setTimeout(resolve, ms));
12
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "rootDir": "src",
5
+ "lib": ["dom", "dom.iterable", "esnext"],
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "esModuleInterop": true,
9
+ "module": "esnext",
10
+ "moduleResolution": "node",
11
+ "resolveJsonModule": true,
12
+ "isolatedModules": true,
13
+ "noImplicitAny": false
14
+ },
15
+ "exclude": ["node_modules", "dist", "scripts"]
16
+ }
@@ -0,0 +1,35 @@
1
+ import { test, expect, describe } from 'vitest';
2
+ import { parseExpression } from '@babel/parser';
3
+ import generate from '@babel/generator';
4
+ import { exportDefaultDeclaration } from '@babel/types';
5
+
6
+ // import { parseExpression } from './utils';
7
+
8
+ test('parseExpression', () => {
9
+ {
10
+ const parsed = parseExpression(
11
+ '{ "type": "StringLiteral", "value": "/api/actions-node" }',
12
+ );
13
+ const code = generate(parsed).code;
14
+ expect(code).toMatchInlineSnapshot(`
15
+ "{
16
+ "type": "StringLiteral",
17
+ "value": "/api/actions-node"
18
+ }"
19
+ `);
20
+ }
21
+ {
22
+ const parsed = parseExpression('null');
23
+ const code = generate(parsed).code;
24
+ expect(code).toMatchInlineSnapshot(`"null"`);
25
+ }
26
+ {
27
+ const parsed = parseExpression(
28
+ `typeof wrapMethod === 'function' ? wrapMethod : undefined`,
29
+ );
30
+ const code = generate(parsed).code;
31
+ expect(code).toMatchInlineSnapshot(
32
+ `"typeof wrapMethod === 'function' ? wrapMethod : undefined"`,
33
+ );
34
+ }
35
+ });
@@ -0,0 +1,56 @@
1
+ import * as babel from '@babel/core';
2
+ import generate from '@babel/generator';
3
+ import * as types from '@babel/types';
4
+ import fs from 'fs';
5
+ import { PluginOptions } from './babelTransformRpc';
6
+ import { default as nodePath, default as path } from 'path';
7
+
8
+ import { getFileName, logger } from './utils';
9
+
10
+ type Babel = { types: typeof types };
11
+
12
+ let deletedDir = false;
13
+
14
+ export default function debugOutputsPlugin(
15
+ { types: t }: Babel,
16
+ { isServer }: PluginOptions,
17
+ ): babel.PluginObj | undefined {
18
+ const cwd = process.cwd();
19
+
20
+ if (!deletedDir) {
21
+ deletedDir = true;
22
+
23
+ fs.mkdirSync('./actions-outputs', { recursive: true });
24
+ }
25
+ return {
26
+ visitor: {
27
+ Program: {
28
+ exit(program, state) {
29
+ const filePath =
30
+ getFileName(state) ?? nodePath.join('pages', 'Default.js');
31
+
32
+ if (!process.env.DEBUG_ACTIONS) {
33
+ return;
34
+ }
35
+
36
+ // stringify the AST and print it
37
+ const output = generate(
38
+ program.node,
39
+ {
40
+ /* options */
41
+ },
42
+ this.file.code,
43
+ );
44
+ let p = path.resolve(
45
+ './actions-outputs',
46
+ isServer ? 'server/' : 'client/',
47
+ path.relative(cwd, path.resolve(filePath)),
48
+ );
49
+ logger.log(`${isServer ? 'server' : 'client'} plugin output:`, p);
50
+ fs.mkdirSync(path.dirname(p), { recursive: true });
51
+ fs.writeFileSync(p, output.code);
52
+ },
53
+ },
54
+ },
55
+ };
56
+ }
@@ -0,0 +1,404 @@
1
+ import * as babel from '@babel/core';
2
+ import generate from '@babel/generator';
3
+ import { parseExpression } from '@babel/parser';
4
+ import type * as types from '@babel/types';
5
+ import fs from 'fs';
6
+ import path from 'path';
7
+ import { WrapMethodMeta } from './server';
8
+ import { annotateAsPure, directive, logger } from './utils';
9
+
10
+ type Babel = { types: typeof types };
11
+
12
+ const { name } = require('../package.json');
13
+ const IMPORT_PATH_SERVER = `${name}/dist/server.js`;
14
+ const IMPORT_PATH_BROWSER = `${name}/dist/browser.js`;
15
+
16
+ function isAllowedTsExportDeclaration(
17
+ declaration: babel.NodePath<babel.types.Declaration | null | undefined>,
18
+ ): boolean {
19
+ return (
20
+ declaration.isTSTypeAliasDeclaration() ||
21
+ declaration.isTSInterfaceDeclaration()
22
+ );
23
+ }
24
+
25
+ const allowedExports = new Set([
26
+ 'revalidate', //
27
+ 'preferredRegion',
28
+ 'runtime',
29
+ 'maxDuration',
30
+ 'fetchCache',
31
+ 'dynamic',
32
+ 'dynamicParams',
33
+ 'GET',
34
+ 'HEAD',
35
+ ]);
36
+
37
+ function getConfigObjectExpression(
38
+ variable: babel.NodePath<babel.types.VariableDeclarator>,
39
+ ) {
40
+ const identifier = variable.get('id');
41
+ const init = variable.get('init');
42
+ if (
43
+ identifier.isIdentifier() &&
44
+ identifier.node.name === 'config' &&
45
+ init.isObjectExpression()
46
+ ) {
47
+ const isEdge = isEdgeInConfig(init);
48
+ return {
49
+ isEdge,
50
+ };
51
+ }
52
+ if (identifier.isIdentifier() && identifier.node.name === 'runtime') {
53
+ const isEdge = init.isStringLiteral({ value: 'edge' });
54
+ return {
55
+ isEdge,
56
+ };
57
+ }
58
+ return null;
59
+ }
60
+
61
+ export function getConfigObject(program: babel.NodePath<babel.types.Program>) {
62
+ for (const statement of program.get('body')) {
63
+ if (statement.isExportNamedDeclaration()) {
64
+ const declaration = statement.get('declaration');
65
+ if (declaration.isVariableDeclaration()) {
66
+ for (const variable of declaration.get('declarations')) {
67
+ const configObject = getConfigObjectExpression(variable);
68
+ if (configObject) {
69
+ return configObject;
70
+ }
71
+ }
72
+ }
73
+ }
74
+ }
75
+ return;
76
+ }
77
+
78
+ function isServerAction(program: babel.NodePath<babel.types.Program>): boolean {
79
+ const dir = program.node.directives?.find(
80
+ (x) => x.value?.value === directive,
81
+ );
82
+ return !!dir;
83
+ // https://regex101.com/r/Wm6UvV/1
84
+ // return /^("|')poor man's use server("|')(;?)\n/m.test(code);
85
+ }
86
+
87
+ function hasWrapMethod(program: babel.NodePath<babel.types.Program>) {
88
+ // check if there is a function export called wrapMethod
89
+ for (const statement of program.get('body')) {
90
+ // also check the export { wrapMethod }
91
+ if (statement.isExportNamedDeclaration()) {
92
+ for (const specifier of statement.get('specifiers')) {
93
+ if (
94
+ specifier.node.exported.type === 'Identifier' &&
95
+ specifier.node.exported.name === 'wrapMethod'
96
+ ) {
97
+ return true;
98
+ }
99
+ }
100
+ }
101
+ if (statement.isExportNamedDeclaration()) {
102
+ const declaration = statement.get('declaration');
103
+ if (declaration.isFunctionDeclaration()) {
104
+ const identifier = declaration.get('id');
105
+ if (identifier.node?.name === 'wrapMethod') {
106
+ return true;
107
+ }
108
+ } else if (declaration.isVariableDeclaration()) {
109
+ for (const variable of declaration.get('declarations')) {
110
+ const id = variable.get('id');
111
+ if (id.isIdentifier() && id.node.name === 'wrapMethod') {
112
+ return true;
113
+ }
114
+ }
115
+ }
116
+ }
117
+ }
118
+
119
+ // return (
120
+ // /export\s+function\s+wrapMethod\s*\(/m.test(code) ||
121
+ // /export\s+(let|const)\s+wrapMethod\s*/m.test(code) ||
122
+ // // https://regex101.com/r/nRaEVs/1
123
+ // /export\s+\{[^}]*wrapMethod/m.test(code)
124
+ // );
125
+ }
126
+
127
+ export function isEdgeInConfig(
128
+ configObject?: babel.NodePath<babel.types.ObjectExpression>,
129
+ ): boolean {
130
+ if (!configObject) {
131
+ return false;
132
+ }
133
+ for (const property of configObject.get('properties')) {
134
+ if (!property.isObjectProperty()) {
135
+ continue;
136
+ }
137
+ const key = property.get('key');
138
+ const value = property.get('value');
139
+
140
+ if (
141
+ property.isObjectProperty() &&
142
+ key.isIdentifier({ name: 'runtime' }) &&
143
+ value.isStringLiteral({ value: 'edge' })
144
+ ) {
145
+ return true;
146
+ }
147
+ }
148
+ return false;
149
+ }
150
+
151
+ export interface PluginOptions {
152
+ isServer: boolean;
153
+ rootDir: string;
154
+
155
+ url?: string;
156
+ }
157
+
158
+ export default function (
159
+ { types: t }: Babel,
160
+ { rootDir, isServer, url: rpcUrl }: PluginOptions,
161
+ ): babel.PluginObj {
162
+ return {
163
+ visitor: {
164
+ Program(program) {
165
+ const { filename } = this.file.opts;
166
+
167
+ if (!filename) {
168
+ return;
169
+ }
170
+
171
+ const { isEdge } = getConfigObject(program) || {
172
+ isEdge: false,
173
+ };
174
+
175
+ const isAction = isServerAction(program);
176
+
177
+ if (!isAction) {
178
+ logger.log(`Skipping ${filename} because it's not an action`);
179
+ return;
180
+ }
181
+
182
+ logger.log(
183
+ `Processing ${filename} as a ${
184
+ isServer ? 'server' : 'client'
185
+ } action`,
186
+ );
187
+
188
+ const hasWrap = hasWrapMethod(program);
189
+
190
+ const rel = path.relative(rootDir, filename);
191
+
192
+ const rpcRelativePath = rel
193
+ .replace(/\.[j|t]sx?$/, '')
194
+ // remove /pages at the start
195
+ .replace(/^src\//, '')
196
+ .replace(/\/index$/, '');
197
+ let rpcPath = rpcUrl
198
+ ? new URL(rpcRelativePath, rpcUrl).toString()
199
+ : '/' + rpcRelativePath;
200
+
201
+ const rpcMethodNames: string[] = [];
202
+
203
+ const createRpcMethodIdentifier =
204
+ program.scope.generateUidIdentifier('createRpcMethod');
205
+
206
+ const createRpcMethod = (
207
+ rpcMethod:
208
+ | babel.types.ArrowFunctionExpression
209
+ | babel.types.FunctionExpression,
210
+ meta: WrapMethodMeta,
211
+ ) => {
212
+ return t.callExpression(createRpcMethodIdentifier, [
213
+ rpcMethod,
214
+ parseExpression(JSON.stringify(meta)),
215
+
216
+ parseExpression(
217
+ hasWrap
218
+ ? `typeof wrapMethod === 'function' ? wrapMethod : undefined`
219
+ : 'null',
220
+ ),
221
+ ]);
222
+ };
223
+
224
+ const generators = new Map<string, boolean>();
225
+ for (const statement of program.get('body')) {
226
+ if (statement.isExportNamedDeclaration()) {
227
+ // check if function is async generator
228
+
229
+ const declaration = statement.get('declaration');
230
+ if (isAllowedTsExportDeclaration(declaration)) {
231
+ // ignore
232
+ } else if (declaration.isFunctionDeclaration()) {
233
+ const identifier = declaration.get('id');
234
+ const methodName = identifier.node?.name;
235
+ if (methodName === 'wrapMethod') {
236
+ continue;
237
+ }
238
+ const isGenerator = !!declaration.node.generator;
239
+ generators.set(methodName!, isGenerator);
240
+ if (!declaration.node.async) {
241
+ throw declaration.buildCodeFrameError(
242
+ 'rpc exports must be async functions',
243
+ );
244
+ }
245
+
246
+ if (methodName) {
247
+ rpcMethodNames.push(methodName);
248
+ if (isServer) {
249
+ // replace with wrapped
250
+ statement.replaceWith(
251
+ t.exportNamedDeclaration(
252
+ t.variableDeclaration('const', [
253
+ t.variableDeclarator(
254
+ t.identifier(methodName),
255
+ createRpcMethod(t.toExpression(declaration.node), {
256
+ name: methodName,
257
+ pathname: rpcPath,
258
+ isGenerator,
259
+ }),
260
+ ),
261
+ ]),
262
+ ),
263
+ );
264
+ }
265
+ }
266
+ } else if (
267
+ declaration.isVariableDeclaration() &&
268
+ declaration.node.kind === 'const'
269
+ ) {
270
+ for (const variable of declaration.get('declarations')) {
271
+ const init = variable.get('init');
272
+
273
+ if (getConfigObjectExpression(variable)) {
274
+ continue;
275
+ }
276
+ const node = variable.get('id');
277
+
278
+ if (node.isIdentifier() && allowedExports.has(node.node.name)) {
279
+ continue;
280
+ }
281
+ if (getConfigObjectExpression(variable)) {
282
+ // ignore, this is the only allowed non-function export
283
+ continue;
284
+ }
285
+ if (
286
+ init.isFunctionExpression() ||
287
+ init.isArrowFunctionExpression()
288
+ ) {
289
+ const { id } = variable.node;
290
+ if (t.isIdentifier(id)) {
291
+ const methodName = id.name;
292
+ if (methodName === 'wrapMethod') {
293
+ continue;
294
+ }
295
+ }
296
+ if (!init.node.async) {
297
+ throw init.buildCodeFrameError(
298
+ 'rpc exports must be async functions',
299
+ );
300
+ }
301
+
302
+ if (t.isIdentifier(id)) {
303
+ const methodName = id.name;
304
+ if (methodName === 'wrapMethod') {
305
+ continue;
306
+ }
307
+ const isGenerator = !!init.node.generator;
308
+ generators.set(methodName!, isGenerator);
309
+ rpcMethodNames.push(methodName);
310
+ if (isServer) {
311
+ init.replaceWith(
312
+ createRpcMethod(init.node, {
313
+ name: methodName,
314
+ pathname: rpcPath,
315
+ isGenerator,
316
+ }),
317
+ );
318
+ }
319
+ }
320
+ } else {
321
+ throw variable.buildCodeFrameError(
322
+ 'rpc exports must be static functions',
323
+ );
324
+ }
325
+ }
326
+ } else {
327
+ for (const specifier of statement.get('specifiers')) {
328
+ if (
329
+ specifier?.node?.exported.type === 'Identifier' &&
330
+ specifier?.node?.exported.name === 'wrapMethod'
331
+ ) {
332
+ continue;
333
+ }
334
+ throw specifier.buildCodeFrameError(
335
+ 'rpc exports must be static functions',
336
+ );
337
+ }
338
+ }
339
+ } else if (statement.isExportDefaultDeclaration()) {
340
+ throw statement.buildCodeFrameError(
341
+ 'default exports are not allowed in rpc routes',
342
+ );
343
+ }
344
+ }
345
+
346
+ if (!isServer) {
347
+ const createRpcFetcherIdentifier =
348
+ program.scope.generateUidIdentifier('createRpcFetcher');
349
+
350
+ // Clear the whole body
351
+ out: for (const statement of program.get('body')) {
352
+ // don't remove if it's an export with name is config or runtime
353
+ if (statement.isExportNamedDeclaration()) {
354
+ const declaration = statement.get('declaration');
355
+ if (declaration.isVariableDeclaration()) {
356
+ for (const variable of declaration.get('declarations')) {
357
+ const configObject = getConfigObjectExpression(variable);
358
+ if (configObject) {
359
+ continue out;
360
+ }
361
+ }
362
+ }
363
+ }
364
+ statement.remove();
365
+ }
366
+
367
+ program.pushContainer('body', [
368
+ t.importDeclaration(
369
+ [
370
+ t.importSpecifier(
371
+ createRpcFetcherIdentifier,
372
+ t.identifier('createRpcFetcher'),
373
+ ),
374
+ ],
375
+ t.stringLiteral(IMPORT_PATH_BROWSER),
376
+ ),
377
+ ...rpcMethodNames.map((name) => {
378
+ const isGenerator = !!generators.get(name);
379
+ return t.exportNamedDeclaration(
380
+ t.variableDeclaration('const', [
381
+ t.variableDeclarator(
382
+ t.identifier(name),
383
+ annotateAsPure(
384
+ t,
385
+ t.callExpression(createRpcFetcherIdentifier, [
386
+ parseExpression(
387
+ JSON.stringify({
388
+ url: rpcPath,
389
+ method: name,
390
+ isGenerator,
391
+ }),
392
+ ),
393
+ ]),
394
+ ),
395
+ ),
396
+ ]),
397
+ );
398
+ }),
399
+ ]);
400
+ }
401
+ },
402
+ },
403
+ };
404
+ }