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/src/browser.ts ADDED
@@ -0,0 +1,142 @@
1
+ import { JsonRpcRequest, JsonRpcResponse } from './jsonRpc';
2
+ import { EventSourceParserStream } from 'eventsource-parser/stream';
3
+ import { generate } from 'fast-glob/out/managers/tasks';
4
+
5
+ import superjson from 'superjson';
6
+
7
+ type NextRpcCall = (...params: any[]) => any | AsyncGenerator<any>;
8
+
9
+ let nextId = 1;
10
+
11
+ export function createRpcFetcher({
12
+ url,
13
+ method,
14
+ isGenerator,
15
+ }: {
16
+ url: string;
17
+ method: string;
18
+ isGenerator?: boolean;
19
+ }): NextRpcCall {
20
+ const controller = new AbortController();
21
+ if (isGenerator) {
22
+ const generator = async function* rpcFetchGenerator(...args) {
23
+ const { json, meta } = superjson.serialize(args);
24
+ const res = await fetch(url, {
25
+ method: 'POST',
26
+ headers: {
27
+ Accept: 'text/event-stream',
28
+ 'Content-Type': 'application/json',
29
+ },
30
+ signal: controller.signal,
31
+ body: JSON.stringify({
32
+ jsonrpc: '2.0',
33
+ id: nextId++,
34
+ method,
35
+ params: json as any[],
36
+ meta,
37
+ } satisfies JsonRpcRequest),
38
+ });
39
+ if (res.status >= 400) {
40
+ const json = await res.json();
41
+ await handleJsonrpcError({ status: res.status, json });
42
+ }
43
+
44
+ if (!res.body) {
45
+ throw new Error('No response body for generator action');
46
+ }
47
+ const eventStream = res.body
48
+ .pipeThrough(new TextDecoderStream())
49
+ .pipeThrough(new EventSourceParserStream())
50
+ .getReader();
51
+ let isClosed = false;
52
+ try {
53
+ while (true) {
54
+ const { value: event, done } = await eventStream.read();
55
+
56
+ if (done) {
57
+ isClosed = true;
58
+ break;
59
+ }
60
+ if (!event) continue;
61
+
62
+ if (event.data === '[DONE]') {
63
+ continue;
64
+ }
65
+
66
+ const json = JSON.parse(event.data);
67
+
68
+ const { jsonrpc, id, result, meta, error } = json as JsonRpcResponse;
69
+ await handleJsonrpcError({ status: res.status, json });
70
+ const deserialized = superjson.deserialize({
71
+ json: result,
72
+ meta,
73
+ });
74
+ yield deserialized;
75
+ }
76
+ } finally {
77
+ // if user calls return() in the generator, we need to close the stream
78
+
79
+ if (!isClosed) {
80
+ // if stream is still open, abort controller
81
+ controller.abort();
82
+ eventStream.cancel();
83
+ }
84
+ }
85
+ };
86
+
87
+ return generator;
88
+ }
89
+ async function rpcFetch(...args) {
90
+ const { json: argsJson, meta } = superjson.serialize(args);
91
+ const res = await fetch(url, {
92
+ method: 'POST',
93
+ body: JSON.stringify(
94
+ {
95
+ jsonrpc: '2.0',
96
+ id: nextId++,
97
+ method,
98
+ params: argsJson as any[],
99
+ meta,
100
+ } satisfies JsonRpcRequest,
101
+ null,
102
+ 2,
103
+ ),
104
+ headers: {
105
+ 'content-type': 'application/json',
106
+ },
107
+ });
108
+ const json = await res.json();
109
+ await handleJsonrpcError({ status: res.status, json });
110
+ {
111
+ const deserialized = superjson.deserialize({
112
+ json: json.result,
113
+ meta: json.meta,
114
+ });
115
+ return deserialized as any;
116
+ }
117
+ }
118
+ rpcFetch.abort = function abort() {
119
+ controller.abort();
120
+ };
121
+ return rpcFetch;
122
+ }
123
+
124
+ async function handleJsonrpcError({
125
+ status,
126
+ json,
127
+ }: {
128
+ status: number;
129
+ json: JsonRpcResponse;
130
+ }) {
131
+ if (status >= 400) {
132
+ const statusError = new Error('Unexpected HTTP status ' + status);
133
+
134
+ if (json?.error && typeof json.error?.message === 'string') {
135
+ let err = new Error(json.error.message);
136
+ Object.assign(err, json.error.data || {});
137
+ throw err;
138
+ }
139
+
140
+ throw statusError;
141
+ }
142
+ }
package/src/build.ts ADDED
@@ -0,0 +1,303 @@
1
+ import { red } from 'picocolors';
2
+ import fsx from 'fs-extra';
3
+
4
+ import {
5
+ ConsoleMessageId,
6
+ Extractor,
7
+ ExtractorConfig,
8
+ ExtractorLogLevel,
9
+ } from '@microsoft/api-extractor';
10
+ import chokidar from 'chokidar';
11
+ import { getPackages } from '@manypkg/get-packages';
12
+
13
+ import { transform } from '@babel/core';
14
+ import { exec } from 'child_process';
15
+ import { globSync } from 'fast-glob';
16
+ import fs from 'fs';
17
+ import path from 'path';
18
+ import { plugins } from '.';
19
+ import { directive, serverEntryName } from './utils';
20
+
21
+ export async function buildOnce({ rootDir, url }) {
22
+ console.log();
23
+ console.log('building functions');
24
+ if (url && !url.endsWith('/')) {
25
+ // make sure that new URL uses the last portion of the path too
26
+ url += '/';
27
+ }
28
+ try {
29
+ new URL(url);
30
+ } catch (e) {
31
+ throw new Error(`Invalid url ${url}`);
32
+ }
33
+
34
+ let libOutDir = path.resolve('dist');
35
+ let serverOutDir = path.resolve('server');
36
+ await fs.promises.rm(libOutDir, { recursive: true }).catch(() => null);
37
+
38
+ const cwd = process.cwd();
39
+ const serverEntrypoint = path.resolve(rootDir, serverEntryName + '.ts');
40
+
41
+ try {
42
+ const globBase = path.relative(cwd, rootDir);
43
+ const globs = [path.posix.join(globBase, '**/*.{ts,tsx,js,jsx}')];
44
+ // console.log({ globs });
45
+ const allPossibleFiles = globSync(globs, {
46
+ onlyFiles: true,
47
+ absolute: true,
48
+ });
49
+ const actionFilesRelativePaths = allPossibleFiles
50
+ .filter((file) => {
51
+ const content = fs.readFileSync(file, 'utf8');
52
+ return content.includes(directive);
53
+ })
54
+ .map((x) => {
55
+ return path.relative(rootDir, x);
56
+ });
57
+ const importsCode = actionFilesRelativePaths
58
+ .map((filePath) => {
59
+ filePath = removeExtension(filePath);
60
+ return `${JSON.stringify(
61
+ '/' + filePath,
62
+ )}: () => import('./${filePath}.js')`;
63
+ })
64
+ .join(',');
65
+ const serverExposeContent =
66
+ `// this file was generated\n` +
67
+ `import { internalEdgeHandler, internalNodeJsHandler } from 'spiceflow/dist/server.js';\n` +
68
+ `export const methodsMap = {${importsCode}}\n` +
69
+ `export const edgeHandler = internalEdgeHandler({ methodsMap });\n` +
70
+ `export const nodeJsHandler = internalNodeJsHandler({ methodsMap });\n`;
71
+
72
+ if (!actionFilesRelativePaths.length) {
73
+ throw new Error('No functions files found!');
74
+ }
75
+ fs.writeFileSync(serverEntrypoint, serverExposeContent, 'utf8');
76
+
77
+ await Promise.all([
78
+ runCommand(
79
+ `tsc --incremental --declaration --noEmit false --outDir ${libOutDir} `,
80
+ ),
81
+ ]).catch((error) => {
82
+ // console.error(error);
83
+ console.error('Error running tsc, continue anyway');
84
+ });
85
+
86
+ // copy tsc output to server dir
87
+ await fsx.copy(libOutDir, serverOutDir, { overwrite: true });
88
+ // rename serverEntryName to index
89
+ const tscOutFiles = fs.readdirSync(serverOutDir);
90
+ for (const file of tscOutFiles) {
91
+ if (file.startsWith(serverEntryName)) {
92
+ const remaining = file.slice(serverEntryName.length);
93
+ fs.renameSync(
94
+ path.resolve(serverOutDir, file),
95
+ path.resolve(serverOutDir, 'index' + remaining),
96
+ );
97
+ }
98
+ }
99
+
100
+ const imports = [] as string[];
101
+
102
+ for (let actionFile of actionFilesRelativePaths) {
103
+ const abs = path.resolve(rootDir, actionFile);
104
+ const content = fs.readFileSync(abs, 'utf8');
105
+
106
+ const actionName = path.basename(actionFile, path.extname(actionFile));
107
+
108
+ const res = transform(content || '', {
109
+ babelrc: false,
110
+ sourceType: 'module',
111
+ plugins: plugins({
112
+ isServer: false,
113
+ url,
114
+ rootDir,
115
+ }),
116
+ filename: abs,
117
+
118
+ sourceMaps: false,
119
+ });
120
+ if (!res || !res.code) {
121
+ console.error(
122
+ `Error transforming ${actionFile}, returned nothing, maybe not an action?`,
123
+ );
124
+ continue;
125
+ }
126
+
127
+ const importPath =
128
+ './' +
129
+ path.posix.join(path.posix.dirname(actionFile), actionName + '.js');
130
+ console.log(`processed ${importPath}`);
131
+ imports.push(importPath);
132
+ fs.mkdirSync(path.resolve(libOutDir, path.dirname(importPath)), {
133
+ recursive: true,
134
+ });
135
+ fs.writeFileSync(path.resolve(libOutDir, importPath), res.code, 'utf-8');
136
+ }
137
+
138
+ // const generator = createGenerator({
139
+ // path: dtsOutputFilePath,
140
+ // type: '*',
141
+ // tsconfig: 'tsconfig.json',
142
+ // skipTypeCheck: true,
143
+ // functions: 'comment',
144
+ // });
145
+ // const schema = generator.createSchema();
146
+ // fs.writeFileSync(
147
+ // path.resolve(outDir, 'schema.json'),
148
+ // JSON.stringify(schema, null, 2),
149
+ // );
150
+
151
+ const bundledPackages = (await getPackages(process.cwd())).packages.map(
152
+ (x) => x.packageJson.name,
153
+ );
154
+ // TODO devDependencies should be bundled too, given these are not shipped with the SDK
155
+ if (!bundledPackages.length) {
156
+ console.log('no workspace packages found, skipping types bundling');
157
+ return;
158
+ }
159
+ for (const actionFile of actionFilesRelativePaths) {
160
+ const entryPointDts = path.resolve(
161
+ libOutDir,
162
+ // path.relative(process.cwd(), rootDir),
163
+ path.dirname(actionFile),
164
+ path.basename(actionFile, path.extname(actionFile)) + '.d.ts',
165
+ );
166
+ console.log(`bundling types for ${path.relative(cwd, entryPointDts)}`);
167
+
168
+ rollupDtsFile({
169
+ bundledPackages,
170
+ inputFilePath: entryPointDts,
171
+ outputFilePath: entryPointDts,
172
+ tsconfigFilePath: 'tsconfig.json',
173
+ });
174
+ }
175
+ } finally {
176
+ await fs.promises.unlink(serverEntrypoint).catch(() => null);
177
+ }
178
+ }
179
+ const logger = console;
180
+
181
+ let isBuilding = { ref: false };
182
+ let missedWatch = { ref: false };
183
+
184
+ export async function build({ rootDir, url, watch = false }) {
185
+ await buildOnce({ rootDir, url });
186
+ if (!watch) {
187
+ return;
188
+ }
189
+ const watcher = chokidar.watch(rootDir, {
190
+ // ignored: /(^|[\/\\])\../, // ignore dotfiles
191
+ ignored: ['**/node_modules/**', '**/dist/**', `src/${serverEntryName}.ts`],
192
+ persistent: true,
193
+ });
194
+ console.log('watching for changes');
195
+ watcher.on('change', async (path, stats) => {
196
+ if (isBuilding.ref) {
197
+ missedWatch.ref = true;
198
+ return;
199
+ }
200
+ isBuilding.ref = true;
201
+ try {
202
+ logger.log(`detected change in ${path}`);
203
+ await buildOnce({ rootDir, url });
204
+ if (missedWatch.ref) {
205
+ // logger.log('missed a change, rebuilding');
206
+ await buildOnce({ rootDir, url });
207
+ missedWatch.ref = false;
208
+ }
209
+ } finally {
210
+ isBuilding.ref = false;
211
+ }
212
+ });
213
+ }
214
+
215
+ function rollupDtsFile({
216
+ inputFilePath,
217
+ outputFilePath,
218
+ tsconfigFilePath,
219
+ bundledPackages,
220
+ }: {
221
+ inputFilePath: string;
222
+ outputFilePath: string;
223
+ tsconfigFilePath: string;
224
+ bundledPackages: string[];
225
+ }) {
226
+ let cwd = process.cwd();
227
+ if (!fs.existsSync(tsconfigFilePath)) {
228
+ throw new Error(`tsconfig.json not found at ${tsconfigFilePath}`);
229
+ }
230
+
231
+ let packageJsonFullPath = path.join(cwd, 'package.json');
232
+
233
+ const extractorConfig = ExtractorConfig.prepare({
234
+ configObject: {
235
+ mainEntryPointFilePath: inputFilePath,
236
+ bundledPackages,
237
+ apiReport: {
238
+ enabled: false,
239
+
240
+ // `reportFileName` is not been used. It's just to fit the requirement of API Extractor.
241
+ reportFileName: 'report.html',
242
+ },
243
+ docModel: { apiJsonFilePath: 'api.json', enabled: false },
244
+ dtsRollup: {
245
+ enabled: true,
246
+ untrimmedFilePath: outputFilePath,
247
+ },
248
+ tsdocMetadata: { enabled: false, tsdocMetadataFilePath: 'another.json' },
249
+ compiler: {
250
+ tsconfigFilePath: tsconfigFilePath,
251
+ },
252
+
253
+ projectFolder: cwd,
254
+ },
255
+ configObjectFullPath: undefined,
256
+ packageJsonFullPath,
257
+ });
258
+
259
+ // Invoke API Extractor
260
+ const extractorResult = Extractor.invoke(extractorConfig, {
261
+ // Equivalent to the "--local" command-line parameter
262
+ localBuild: true,
263
+
264
+ messageCallback: (message) => {
265
+ switch (message.messageId) {
266
+ case ConsoleMessageId.ApiReportCreated:
267
+ message.logLevel = ExtractorLogLevel.None;
268
+ break;
269
+ case ConsoleMessageId.Preamble:
270
+ message.logLevel = ExtractorLogLevel.None;
271
+ break;
272
+ }
273
+ },
274
+ showDiagnostics: false,
275
+ // Equivalent to the "--verbose" command-line parameter
276
+ showVerboseMessages: false,
277
+ });
278
+
279
+ if (!extractorResult.succeeded) {
280
+ throw new Error(
281
+ `API Extractor completed with ${extractorResult.errorCount} errors and ${extractorResult.warningCount} warnings when processing ${inputFilePath}`,
282
+ );
283
+ }
284
+ }
285
+
286
+ function removeExtension(filePath: string) {
287
+ return filePath.replace(/\.[j|t]sx?$/, '');
288
+ }
289
+
290
+ function runCommand(command: string) {
291
+ return new Promise((resolve, reject) => {
292
+ exec(command, {}, (error, stdout, stderr) => {
293
+ if (error) {
294
+ console.log();
295
+ console.error(red(stdout));
296
+ console.error(red(stderr));
297
+ reject(error);
298
+ } else {
299
+ resolve(stdout);
300
+ }
301
+ });
302
+ });
303
+ }
package/src/cli.ts ADDED
@@ -0,0 +1,118 @@
1
+ #! /usr/bin/env node
2
+ import os from 'os';
3
+ import fsx from 'fs-extra';
4
+
5
+ import fs from 'fs-extra';
6
+
7
+ import { cac } from 'cac';
8
+ import { build, buildOnce } from './build.js';
9
+ import { findRootDir } from './index.js';
10
+ import { exec, execSync, spawn } from 'child_process';
11
+ import path from 'path';
12
+
13
+ export const cli = cac();
14
+
15
+ cli
16
+ .command('', 'Generate an SDK package for your functions')
17
+ .option('--watch', 'Watch for changes')
18
+ .option('--url <url>', 'URL of the package, including the base path', {
19
+ default: 'http://localhost:3333',
20
+ })
21
+ .action(async (options) => {
22
+ const { url, watch } = options;
23
+ const rootDir = await findRootDir(process.cwd());
24
+ await build({ rootDir, url, watch });
25
+ });
26
+ cli
27
+ .command('init', 'Generate a new spiceflow project')
28
+ .option('--name <name>', 'Name of this project')
29
+ .action(async (options) => {
30
+ // copy contents of the template dir here
31
+ const { name } = options;
32
+ if (!name) {
33
+ throw new Error('--name is required');
34
+ }
35
+ fsx.copySync(path.resolve(__dirname, '../sdk-template'), name, {
36
+ filter(pathname) {
37
+ return (
38
+ !pathname.includes('node_modules') &&
39
+ !pathname.includes('dist') &&
40
+ !pathname.endsWith('.tsbuildinfo')
41
+ );
42
+ },
43
+ });
44
+ // replace the package.json name
45
+ const packageJsonPath = path.resolve(name, 'package.json');
46
+ const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
47
+ packageJson.name = name;
48
+ delete packageJson.private;
49
+ // replace workspace:* with * from the package.json
50
+ for (const key of Object.keys(packageJson.dependencies || {})) {
51
+ const value = packageJson.dependencies[key];
52
+ if (value && value.startsWith('workspace:')) {
53
+ packageJson.dependencies[key] = '*';
54
+ }
55
+ }
56
+
57
+ fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
58
+ });
59
+ cli
60
+ .command('serve', 'Expose a server for your functions')
61
+ .option('--basePath', 'base path for the server', { default: '/' })
62
+ .option('--port <port>', 'Port to listen on', { default: '3333' })
63
+ .option('--watch', 'Watch for changes')
64
+ .option(
65
+ '--skip-build',
66
+ 'Skip building the server and SDK, you must build it yourself first',
67
+ )
68
+ .action(async (options) => {
69
+ let { basePath, skipBuild, watch, port } = options;
70
+ const nodePath = process.execPath || 'node';
71
+ const rootDir = await findRootDir(process.cwd());
72
+ if (!skipBuild) {
73
+ await build({ rootDir, watch, url: `http://127.0.0.1:${port}` });
74
+ }
75
+
76
+ const tempFilePath = path.resolve('_main.mjs');
77
+
78
+ const code = `import { methodsMap } from './server/index.js'; import { exposeNodeServer } from 'spiceflow/dist/expose.js'; exposeNodeServer({ methodsMap, basePath: '${basePath}', port: ${port} });`;
79
+ fs.writeFileSync(tempFilePath, code);
80
+ process.on('SIGINT', () => {
81
+ try {
82
+ fs.unlinkSync(tempFilePath);
83
+ } catch {}
84
+ process.exit(0);
85
+ });
86
+ // only enable watch if it's supported by node version
87
+ const major = parseInt(process.version.replace('v', '').split('.')[0]);
88
+ if (major && major < 18) {
89
+ console.log(`node version ${process.version} does not support --watch`);
90
+ watch = false;
91
+ }
92
+ try {
93
+ await new Promise<void>((resolve, reject) => {
94
+ const p = spawn(
95
+ `${nodePath} --no-warnings ${watch ? '--watch' : ''} ${JSON.stringify(
96
+ tempFilePath,
97
+ )}`,
98
+ {
99
+ stdio: 'inherit',
100
+ shell: true,
101
+ },
102
+ );
103
+ p.on('close', (code) => {
104
+ if (code === 0) {
105
+ resolve();
106
+ } else {
107
+ reject();
108
+ }
109
+ });
110
+ });
111
+ } finally {
112
+ fs.unlinkSync(tempFilePath);
113
+ }
114
+ });
115
+
116
+ cli.help();
117
+
118
+ cli.parse();
@@ -0,0 +1,36 @@
1
+ import { AsyncLocalStorage } from 'async_hooks';
2
+ import type { IncomingMessage, ServerResponse } from 'http';
3
+
4
+ interface CommonContext {
5
+ // cookies(): ReadonlyRequestCookies;
6
+ // headers(): ReadonlyHeaders;
7
+ }
8
+ interface NodejsContext extends CommonContext {
9
+ req?: IncomingMessage;
10
+ res?: ServerResponse;
11
+ }
12
+ interface EdgeContext extends CommonContext {
13
+ req?: Request;
14
+ res?: Response;
15
+ }
16
+
17
+ const DEFAULT_CONTEXT = {
18
+ // headers() {},
19
+ // cookies() {},
20
+ };
21
+
22
+ export const asyncLocalStorage = new AsyncLocalStorage<
23
+ NodejsContext | EdgeContext
24
+ >();
25
+
26
+ export function getNodejsContext(): NodejsContext {
27
+ return (asyncLocalStorage.getStore() as NodejsContext) || DEFAULT_CONTEXT;
28
+ }
29
+
30
+ export function getEdgeContext(): EdgeContext {
31
+ return (asyncLocalStorage.getStore() as EdgeContext) || DEFAULT_CONTEXT;
32
+ }
33
+
34
+ export function getContext(): CommonContext {
35
+ return asyncLocalStorage.getStore() || DEFAULT_CONTEXT;
36
+ }
package/src/context.ts ADDED
@@ -0,0 +1 @@
1
+ export { getNodejsContext, getEdgeContext, getContext } from './context-internal';
package/src/expose.ts ADDED
@@ -0,0 +1,34 @@
1
+ import http from 'http';
2
+ import { internalNodeJsHandler } from './server';
3
+
4
+ export async function exposeNodeServer({ methodsMap, basePath, port }) {
5
+ const handler = internalNodeJsHandler({ methodsMap });
6
+
7
+ const server = http.createServer(async (req, res) => {
8
+ let ended = false;
9
+ res.on('close', () => {
10
+ ended = true;
11
+ });
12
+
13
+ try {
14
+ if (req.method === 'GET' && req.url === '/') {
15
+ res.statusCode = 200;
16
+ res.end('ok');
17
+ return;
18
+ }
19
+ console.log(`[spiceflow] ${req.url}`);
20
+ await handler({ req, res, basePath });
21
+ } catch (error) {
22
+ if (ended) {
23
+ return;
24
+ }
25
+ console.error(error);
26
+
27
+ res.statusCode = 500;
28
+ res.end('Internal server error');
29
+ }
30
+ });
31
+ server.listen(port, () => {
32
+ console.log(`server listening on http://127.0.0.1:${port}`);
33
+ });
34
+ }
package/src/headers.ts ADDED
@@ -0,0 +1,19 @@
1
+ // import { asyncLocalStorage } from './context-internal';
2
+
3
+ // function myHeaders() {
4
+ // const res = asyncLocalStorage.getStore()?.headers();
5
+ // if (!res) {
6
+ // throw new Error('Called headers() outside of app dir or rpc actions');
7
+ // }
8
+ // return res;
9
+ // }
10
+
11
+ // function myCookies() {
12
+ // const res = asyncLocalStorage.getStore()?.cookies();
13
+ // if (!res) {
14
+ // throw new Error('Called cookies() outside of app dir or rpc actions');
15
+ // }
16
+ // return res;
17
+ // }
18
+
19
+ // export { myCookies as cookies, myHeaders as headers };
package/src/index.ts ADDED
@@ -0,0 +1,42 @@
1
+ import * as fs from 'fs';
2
+ import * as path from 'path';
3
+ import { PluginOptions } from './babelTransformRpc';
4
+
5
+ import { WrapMethod } from './server';
6
+
7
+ export interface WithRpcConfig {}
8
+
9
+ export { WrapMethod };
10
+
11
+ export function plugins({
12
+ isServer,
13
+ rootDir: nextDir,
14
+ url,
15
+ }: PluginOptions) {
16
+ const rpcPluginOptions: PluginOptions = {
17
+ isServer,
18
+ rootDir: nextDir,
19
+ url,
20
+ };
21
+
22
+ return [
23
+ require.resolve('@babel/plugin-syntax-jsx'),
24
+ [require.resolve('@babel/plugin-transform-typescript'), { isTSX: true }],
25
+ [require.resolve('../dist/babelTransformRpc'), rpcPluginOptions],
26
+ process.env.DEBUG_ACTIONS && [
27
+ require.resolve('../dist/babelDebugOutputs'),
28
+ rpcPluginOptions,
29
+ ],
30
+ ].filter(Boolean) as any[];
31
+ }
32
+
33
+ export function findRootDir(dir: string): string {
34
+ {
35
+ let curDir = path.resolve(dir, 'src');
36
+ if (fs.existsSync(curDir)) return path.resolve(curDir);
37
+ }
38
+
39
+ throw new Error(
40
+ "Couldn't find a src directory. Please create one under the project root",
41
+ );
42
+ }