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.
- package/README.md +178 -0
- package/context.d.ts +2 -0
- package/context.js +1 -0
- package/dist/babel.test.d.ts +2 -0
- package/dist/babel.test.d.ts.map +1 -0
- package/dist/babel.test.js +32 -0
- package/dist/babel.test.js.map +1 -0
- package/dist/babelDebugOutputs.d.ts +9 -0
- package/dist/babelDebugOutputs.d.ts.map +1 -0
- package/dist/babelDebugOutputs.js +40 -0
- package/dist/babelDebugOutputs.js.map +1 -0
- package/dist/babelTransformRpc.d.ts +17 -0
- package/dist/babelTransformRpc.d.ts.map +1 -0
- package/dist/babelTransformRpc.js +304 -0
- package/dist/babelTransformRpc.js.map +1 -0
- package/dist/browser.d.ts +8 -0
- package/dist/browser.d.ts.map +1 -0
- package/dist/browser.js +133 -0
- package/dist/browser.js.map +1 -0
- package/dist/build.d.ts +10 -0
- package/dist/build.d.ts.map +1 -0
- package/dist/build.js +253 -0
- package/dist/build.js.map +1 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +108 -0
- package/dist/cli.js.map +1 -0
- package/dist/context-internal.d.ts +20 -0
- package/dist/context-internal.d.ts.map +1 -0
- package/dist/context-internal.js +22 -0
- package/dist/context-internal.js.map +1 -0
- package/dist/context.d.ts +2 -0
- package/dist/context.d.ts.map +1 -0
- package/dist/context.js +8 -0
- package/dist/context.js.map +1 -0
- package/dist/expose.d.ts +6 -0
- package/dist/expose.d.ts.map +1 -0
- package/dist/expose.js +39 -0
- package/dist/expose.js.map +1 -0
- package/dist/headers.d.ts +1 -0
- package/dist/headers.d.ts.map +1 -0
- package/dist/headers.js +18 -0
- package/dist/headers.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/dist/jsonRpc.d.ts +32 -0
- package/dist/jsonRpc.d.ts.map +1 -0
- package/dist/jsonRpc.js +4 -0
- package/dist/jsonRpc.js.map +1 -0
- package/dist/server.d.ts +32 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +301 -0
- package/dist/server.js.map +1 -0
- package/dist/utils.d.ts +20 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +44 -0
- package/dist/utils.js.map +1 -0
- package/headers.d.ts +2 -0
- package/headers.js +1 -0
- package/package.json +56 -0
- package/sdk-template/package.json +22 -0
- package/sdk-template/src/index.ts +2 -0
- package/sdk-template/src/v1/example.ts +5 -0
- package/sdk-template/src/v1/generator.ts +12 -0
- package/sdk-template/tsconfig.json +16 -0
- package/src/babel.test.ts +35 -0
- package/src/babelDebugOutputs.ts +56 -0
- package/src/babelTransformRpc.ts +404 -0
- package/src/browser.ts +142 -0
- package/src/build.ts +303 -0
- package/src/cli.ts +118 -0
- package/src/context-internal.ts +36 -0
- package/src/context.ts +1 -0
- package/src/expose.ts +34 -0
- package/src/headers.ts +19 -0
- package/src/index.ts +42 -0
- package/src/jsonRpc.ts +43 -0
- package/src/server.ts +384 -0
- 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,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
|
+
}
|