@faasjs/dev 8.0.0-beta.5

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2019-present, Zhu Feng
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # @faasjs/dev
2
+
3
+ FaasJS development toolkit for local development and testing.
4
+
5
+ [![License: MIT](https://img.shields.io/npm/l/@faasjs/dev.svg)](https://github.com/faasjs/faasjs/blob/main/packages/dev/LICENSE)
6
+ [![NPM Version](https://img.shields.io/npm/v/@faasjs/dev.svg)](https://www.npmjs.com/package/@faasjs/dev)
7
+
8
+ ## Install
9
+
10
+ ```sh
11
+ npm install @faasjs/dev
12
+ ```
13
+
14
+ ## Features
15
+
16
+ - Vite integration for in-process FaasJS API during local development.
17
+ - PGlite helpers for lightweight database setup in tests.
18
+ - Test helpers to invoke and assert FaasJS functions.
19
+
20
+ ## Usage: Vite integration
21
+
22
+ ```ts
23
+ import { viteFaasJsServer } from '@faasjs/dev'
24
+
25
+ export default defineConfig({
26
+ plugins: [viteFaasJsServer()],
27
+ })
28
+ ```
29
+
30
+ ## Usage: PGlite helpers
31
+
32
+ ```ts
33
+ import {
34
+ createPgliteKnex,
35
+ mountFaasKnex,
36
+ unmountFaasKnex,
37
+ } from '@faasjs/dev'
38
+
39
+ const db = createPgliteKnex()
40
+ mountFaasKnex(db)
41
+
42
+ // run tests...
43
+
44
+ await db.destroy()
45
+ unmountFaasKnex()
46
+ ```
47
+
48
+ ## Usage: Test helpers
49
+
50
+ ```ts
51
+ import { test } from '@faasjs/dev'
52
+ import Func from '../demo.func.ts'
53
+
54
+ const func = test(Func)
55
+ const response = await func.JSONhandler({ name: 'FaasJS' })
56
+
57
+ expect(response.statusCode).toBe(200)
58
+ expect(response.data).toEqual({ message: 'Hello, FaasJS' })
59
+ ```
60
+
61
+ ## API
62
+
63
+ - Vite: [viteFaasJsServer](functions/viteFaasJsServer.md), [ViteFaasJsServerOptions](type-aliases/ViteFaasJsServerOptions.md)
64
+ - PGlite: [createPgliteKnex](functions/createPgliteKnex.md), [mountFaasKnex](functions/mountFaasKnex.md), [runPgliteSql](functions/runPgliteSql.md), [runPgliteSqlFile](functions/runPgliteSqlFile.md), [unmountFaasKnex](functions/unmountFaasKnex.md), [MountFaasKnexOptions](type-aliases/MountFaasKnexOptions.md)
65
+ - Test: [test](functions/test.md), [FuncWarper](classes/FuncWarper.md), [streamToString](functions/streamToString.md)
66
+
67
+ ## Functions
68
+
69
+ - [createPgliteKnex](functions/createPgliteKnex.md)
70
+ - [defineFunc](functions/defineFunc.md)
71
+ - [mountFaasKnex](functions/mountFaasKnex.md)
72
+ - [nameFunc](functions/nameFunc.md)
73
+ - [parseFuncFilenameFromStack](functions/parseFuncFilenameFromStack.md)
74
+ - [runPgliteSql](functions/runPgliteSql.md)
75
+ - [runPgliteSqlFile](functions/runPgliteSqlFile.md)
76
+ - [streamToString](functions/streamToString.md)
77
+ - [test](functions/test.md)
78
+ - [unmountFaasKnex](functions/unmountFaasKnex.md)
79
+ - [useFunc](functions/useFunc.md)
80
+ - [usePlugin](functions/usePlugin.md)
81
+ - [viteFaasJsServer](functions/viteFaasJsServer.md)
82
+
83
+ ## Classes
84
+
85
+ - [Func](classes/Func.md)
86
+ - [FuncWarper](classes/FuncWarper.md)
87
+
88
+ ## Type Aliases
89
+
90
+ - [Config](type-aliases/Config.md)
91
+ - [ExportedHandler](type-aliases/ExportedHandler.md)
92
+ - [FuncConfig](type-aliases/FuncConfig.md)
93
+ - [FuncEventType](type-aliases/FuncEventType.md)
94
+ - [FuncReturnType](type-aliases/FuncReturnType.md)
95
+ - [Handler](type-aliases/Handler.md)
96
+ - [InvokeData](type-aliases/InvokeData.md)
97
+ - [LifeCycleKey](type-aliases/LifeCycleKey.md)
98
+ - [MountData](type-aliases/MountData.md)
99
+ - [MountFaasKnexOptions](type-aliases/MountFaasKnexOptions.md)
100
+ - [Next](type-aliases/Next.md)
101
+ - [Plugin](type-aliases/Plugin.md)
102
+ - [UseifyPlugin](type-aliases/UseifyPlugin.md)
103
+ - [ViteFaasJsServerOptions](type-aliases/ViteFaasJsServerOptions.md)
package/dist/index.cjs ADDED
@@ -0,0 +1,356 @@
1
+ 'use strict';
2
+
3
+ var fs = require('fs');
4
+ var knex = require('knex');
5
+ var PgliteDialect = require('knex-pglite');
6
+ var zlib = require('zlib');
7
+ var deep_merge = require('@faasjs/deep_merge');
8
+ var http = require('@faasjs/http');
9
+ var load = require('@faasjs/load');
10
+ var logger = require('@faasjs/logger');
11
+ var func_star = require('@faasjs/func');
12
+ var path = require('path');
13
+ var server = require('@faasjs/server');
14
+
15
+ function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
16
+
17
+ function _interopNamespace(e) {
18
+ if (e && e.__esModule) return e;
19
+ var n = Object.create(null);
20
+ if (e) {
21
+ Object.keys(e).forEach(function (k) {
22
+ if (k !== 'default') {
23
+ var d = Object.getOwnPropertyDescriptor(e, k);
24
+ Object.defineProperty(n, k, d.get ? d : {
25
+ enumerable: true,
26
+ get: function () { return e[k]; }
27
+ });
28
+ }
29
+ });
30
+ }
31
+ n.default = e;
32
+ return Object.freeze(n);
33
+ }
34
+
35
+ var PgliteDialect__default = /*#__PURE__*/_interopDefault(PgliteDialect);
36
+ var func_star__namespace = /*#__PURE__*/_interopNamespace(func_star);
37
+
38
+ var __defProp = Object.defineProperty;
39
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
40
+ var __getOwnPropNames = Object.getOwnPropertyNames;
41
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
42
+ var __export = (target, all) => {
43
+ for (var name in all)
44
+ __defProp(target, name, { get: all[name], enumerable: true });
45
+ };
46
+ var __copyProps = (to, from, except, desc) => {
47
+ if (from && typeof from === "object" || typeof from === "function") {
48
+ for (let key of __getOwnPropNames(from))
49
+ if (!__hasOwnProp.call(to, key) && key !== except)
50
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
51
+ }
52
+ return to;
53
+ };
54
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget);
55
+
56
+ // src/index.ts
57
+ var index_exports = {};
58
+ __export(index_exports, {
59
+ FuncWarper: () => FuncWarper,
60
+ createPgliteKnex: () => createPgliteKnex,
61
+ mountFaasKnex: () => mountFaasKnex,
62
+ runPgliteSql: () => runPgliteSql,
63
+ runPgliteSqlFile: () => runPgliteSqlFile,
64
+ streamToString: () => streamToString,
65
+ test: () => test,
66
+ unmountFaasKnex: () => unmountFaasKnex,
67
+ viteFaasJsServer: () => viteFaasJsServer
68
+ });
69
+ function createPgliteKnex(config = {}, connection = {}) {
70
+ return knex.knex({
71
+ ...config,
72
+ client: PgliteDialect__default.default,
73
+ connection
74
+ });
75
+ }
76
+ function mountFaasKnex(db, options = {}) {
77
+ const globalWithFaasKnex = globalThis;
78
+ const name = options.name || "knex";
79
+ if (!globalWithFaasKnex.FaasJS_Knex) globalWithFaasKnex.FaasJS_Knex = {};
80
+ globalWithFaasKnex.FaasJS_Knex[name] = {
81
+ adapter: db,
82
+ query: db,
83
+ config: options.config || {}
84
+ };
85
+ }
86
+ function unmountFaasKnex(name = "knex") {
87
+ const globalWithFaasKnex = globalThis;
88
+ if (!globalWithFaasKnex.FaasJS_Knex) return;
89
+ delete globalWithFaasKnex.FaasJS_Knex[name];
90
+ }
91
+ async function runPgliteSql(db, sql) {
92
+ if (!sql.trim()) return;
93
+ await db.raw(sql);
94
+ }
95
+ async function runPgliteSqlFile(db, filePath, options = {}) {
96
+ const stripUuidOsspExtension = options.stripUuidOsspExtension !== false;
97
+ let sql = fs.readFileSync(filePath, "utf8");
98
+ if (stripUuidOsspExtension)
99
+ sql = sql.replace(/CREATE EXTENSION IF NOT EXISTS "uuid-ossp";\s*/gi, "");
100
+ await runPgliteSql(db, sql);
101
+ }
102
+
103
+ // src/test.ts
104
+ var test_exports = {};
105
+ __export(test_exports, {
106
+ FuncWarper: () => FuncWarper,
107
+ streamToString: () => streamToString,
108
+ test: () => test
109
+ });
110
+ __reExport(test_exports, func_star__namespace);
111
+ async function streamToString(stream) {
112
+ if (!(stream instanceof ReadableStream))
113
+ throw new TypeError("stream must be a ReadableStream instance");
114
+ const reader = stream.getReader();
115
+ const chunks = [];
116
+ try {
117
+ while (true) {
118
+ const { done, value } = await reader.read();
119
+ if (done) break;
120
+ if (value) chunks.push(value);
121
+ }
122
+ } finally {
123
+ reader.releaseLock();
124
+ }
125
+ const decoder = new TextDecoder();
126
+ return decoder.decode(Buffer.concat(chunks.map((c) => Buffer.from(c))));
127
+ }
128
+ var FuncWarper = class {
129
+ file;
130
+ staging;
131
+ logger;
132
+ func;
133
+ config;
134
+ plugins;
135
+ _handler;
136
+ /**
137
+ * @param file {string} Full file path
138
+ * @param func {Func} A FaasJs function
139
+ * ```ts
140
+ * import { FuncWarper } from '@faasjs/dev'
141
+ *
142
+ * new FuncWarper(__dirname + '/../demo.func.ts')
143
+ * ```
144
+ */
145
+ constructor(initBy) {
146
+ this.staging = process.env.FaasEnv;
147
+ this.logger = new logger.Logger("TestCase");
148
+ this.func = initBy.default ? initBy.default : initBy;
149
+ if (this.func.filename)
150
+ this.func.config = deep_merge.deepMerge(
151
+ load.loadConfig(process.cwd(), initBy.filename, this.staging, this.logger),
152
+ initBy.config
153
+ );
154
+ this.plugins = this.func.plugins || [];
155
+ for (const plugin of this.plugins) {
156
+ if (["handler", "config", "plugins", "logger", "mount"].includes(
157
+ plugin.type
158
+ ))
159
+ continue;
160
+ this[plugin.type] = plugin;
161
+ }
162
+ this._handler = this.func.export().handler;
163
+ }
164
+ async mount(handler) {
165
+ if (!this.func.mounted) await this.func.mount();
166
+ if (handler) await handler(this);
167
+ }
168
+ async handler(event = /* @__PURE__ */ Object.create(null), context = /* @__PURE__ */ Object.create(null)) {
169
+ await this.mount();
170
+ const response = await this._handler(event, context);
171
+ this.logger.debug("response: %j", response);
172
+ return response;
173
+ }
174
+ async JSONhandler(body, options = /* @__PURE__ */ Object.create(null)) {
175
+ await this.mount();
176
+ const headers = options.headers || /* @__PURE__ */ Object.create(null);
177
+ if (this.http && this.http instanceof http.Http) {
178
+ if (options.cookie)
179
+ for (const key in options.cookie)
180
+ this.http.cookie.write(key, options.cookie[key]);
181
+ if (options.session) {
182
+ for (const key in options.session)
183
+ this.http.session.write(key, options.session[key]);
184
+ this.http.session.update();
185
+ }
186
+ const cookie = this.http.cookie.headers()["Set-Cookie"]?.map((c) => c.split(";")[0]).join(";");
187
+ if (cookie)
188
+ if (headers.cookie) headers.cookie += `;${cookie}`;
189
+ else headers.cookie = cookie;
190
+ }
191
+ const response = await this._handler({
192
+ httpMethod: "POST",
193
+ headers: Object.assign({ "content-type": "application/json" }, headers),
194
+ body: typeof body === "string" ? body : JSON.stringify(body)
195
+ });
196
+ if (response?.body instanceof ReadableStream) {
197
+ let stream = response.body;
198
+ const encoding = response.headers?.["Content-Encoding"] || response.headers?.["content-encoding"];
199
+ if (encoding) {
200
+ const chunks = [];
201
+ const reader = stream.getReader();
202
+ try {
203
+ while (true) {
204
+ const { done, value } = await reader.read();
205
+ if (done) break;
206
+ if (value) chunks.push(value);
207
+ }
208
+ } catch (error) {
209
+ this.logger.error("Failed to read ReadableStream: %s", error);
210
+ response.body = JSON.stringify({
211
+ error: { message: error.message }
212
+ });
213
+ response.error = { message: error.message };
214
+ response.statusCode = 500;
215
+ reader.releaseLock();
216
+ return response;
217
+ }
218
+ reader.releaseLock();
219
+ const compressedBuffer = Buffer.concat(chunks);
220
+ try {
221
+ let decompressed;
222
+ if (encoding === "br") {
223
+ decompressed = zlib.brotliDecompressSync(compressedBuffer);
224
+ } else if (encoding === "gzip") {
225
+ decompressed = zlib.gunzipSync(compressedBuffer);
226
+ } else if (encoding === "deflate") {
227
+ decompressed = zlib.inflateSync(compressedBuffer);
228
+ } else {
229
+ throw new Error(`Unsupported encoding: ${encoding}`);
230
+ }
231
+ stream = new ReadableStream({
232
+ start(controller) {
233
+ controller.enqueue(new Uint8Array(decompressed));
234
+ controller.close();
235
+ }
236
+ });
237
+ } catch (error) {
238
+ this.logger.error("Failed to decompress: %s", error);
239
+ response.body = JSON.stringify({
240
+ error: { message: error.message }
241
+ });
242
+ response.error = { message: error.message };
243
+ response.statusCode = 500;
244
+ return response;
245
+ }
246
+ }
247
+ try {
248
+ response.body = await streamToString(stream);
249
+ } catch (error) {
250
+ this.logger.error("Failed to decode ReadableStream: %s", error);
251
+ response.body = JSON.stringify({
252
+ error: { message: error.message }
253
+ });
254
+ response.error = { message: error.message };
255
+ response.statusCode = 500;
256
+ }
257
+ }
258
+ if (response?.headers && response.body && response.headers["content-type"]?.includes("json")) {
259
+ const parsedBody = JSON.parse(response.body);
260
+ response.data = parsedBody.data;
261
+ response.error = parsedBody.error;
262
+ }
263
+ if (this.http) {
264
+ response.cookie = this.http.cookie.content;
265
+ response.session = this.http.session.content;
266
+ }
267
+ this.logger.debug("response: %j", response);
268
+ return response;
269
+ }
270
+ };
271
+ function test(initBy) {
272
+ const warper = new FuncWarper(initBy);
273
+ warper.mount = warper.mount.bind(warper);
274
+ warper.handler = warper.handler.bind(warper);
275
+ warper.JSONhandler = warper.JSONhandler.bind(warper);
276
+ return warper;
277
+ }
278
+
279
+ // src/index.ts
280
+ __reExport(index_exports, test_exports);
281
+ function normalizeBase(base) {
282
+ const normalized = base.startsWith("/") ? base : `/${base}`;
283
+ if (normalized === "/") return "/";
284
+ return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
285
+ }
286
+ function stripBase(url, base) {
287
+ if (base === "/") return url;
288
+ const queryIndex = url.indexOf("?");
289
+ const pathname = queryIndex >= 0 ? url.slice(0, queryIndex) : url;
290
+ const search = queryIndex >= 0 ? url.slice(queryIndex) : "";
291
+ if (pathname === base) return `/${search}`;
292
+ if (pathname.startsWith(`${base}/`))
293
+ return `${pathname.slice(base.length)}${search}`;
294
+ return url;
295
+ }
296
+ function viteFaasJsServer(options = {}) {
297
+ let config;
298
+ let server$1 = null;
299
+ const logger$1 = new logger.Logger("FaasJs:Vite");
300
+ return {
301
+ name: "vite:faasjs",
302
+ enforce: "pre",
303
+ configResolved(resolvedConfig) {
304
+ const root = options.root || resolvedConfig.root;
305
+ const base = normalizeBase(options.base || resolvedConfig.base);
306
+ config = {
307
+ root,
308
+ base
309
+ };
310
+ },
311
+ configureServer: async ({ middlewares }) => {
312
+ if (process.env.VITEST) {
313
+ logger$1.debug("Skipping faas server in vitest environment");
314
+ return;
315
+ }
316
+ if (!config) throw new Error("viteFaasJsServer: config is not resolved");
317
+ server$1 = new server.Server(path.join(config.root, "src"));
318
+ middlewares.use(async (req, res, next) => {
319
+ if (!req.url || req.method !== "POST" || !server$1) return next();
320
+ const originalUrl = req.url;
321
+ const strippedUrl = stripBase(req.url, config.base);
322
+ req.url = strippedUrl;
323
+ try {
324
+ logger$1.debug(`Request ${req.url}`);
325
+ await server$1.handle(req, res, {
326
+ requestedAt: Date.now()
327
+ });
328
+ } catch (error) {
329
+ logger$1.error(error);
330
+ if (!res.headersSent && !res.writableEnded) {
331
+ res.writeHead(500, { "Content-Type": "application/json" });
332
+ res.write(
333
+ JSON.stringify({
334
+ error: { message: "Internal Server Error" }
335
+ })
336
+ );
337
+ res.end();
338
+ }
339
+ } finally {
340
+ req.url = originalUrl;
341
+ }
342
+ if (!res.writableEnded) next();
343
+ });
344
+ }
345
+ };
346
+ }
347
+
348
+ exports.FuncWarper = FuncWarper;
349
+ exports.createPgliteKnex = createPgliteKnex;
350
+ exports.mountFaasKnex = mountFaasKnex;
351
+ exports.runPgliteSql = runPgliteSql;
352
+ exports.runPgliteSqlFile = runPgliteSqlFile;
353
+ exports.streamToString = streamToString;
354
+ exports.test = test;
355
+ exports.unmountFaasKnex = unmountFaasKnex;
356
+ exports.viteFaasJsServer = viteFaasJsServer;
@@ -0,0 +1,131 @@
1
+ import { Knex } from 'knex';
2
+ import { Func, Config, Plugin } from '@faasjs/func';
3
+ export * from '@faasjs/func';
4
+ import { Logger } from '@faasjs/logger';
5
+ import { Plugin as Plugin$1 } from 'vite';
6
+
7
+ type MountFaasKnexOptions = {
8
+ /** key of `globalThis.FaasJS_Knex`, default is `knex` */
9
+ name?: string;
10
+ /** optional config metadata passed through to `@faasjs/knex` */
11
+ config?: Record<string, unknown>;
12
+ };
13
+ /**
14
+ * Create a knex instance backed by `knex-pglite`.
15
+ */
16
+ declare function createPgliteKnex(config?: Partial<Knex.Config>, connection?: Record<string, unknown>): Knex;
17
+ /**
18
+ * Mount a knex adapter to `globalThis.FaasJS_Knex` for `@faasjs/knex`.
19
+ */
20
+ declare function mountFaasKnex(db: Knex, options?: MountFaasKnexOptions): void;
21
+ /**
22
+ * Remove mounted knex adapter from `globalThis.FaasJS_Knex`.
23
+ */
24
+ declare function unmountFaasKnex(name?: string): void;
25
+ /**
26
+ * Run a SQL string on a PGlite-backed knex instance.
27
+ */
28
+ declare function runPgliteSql(db: Knex, sql: string): Promise<void>;
29
+ /**
30
+ * Run SQL from file on a PGlite-backed knex instance.
31
+ */
32
+ declare function runPgliteSqlFile(db: Knex, filePath: string, options?: {
33
+ stripUuidOsspExtension?: boolean;
34
+ }): Promise<void>;
35
+
36
+ /**
37
+ * Convert ReadableStream to string.
38
+ * @param stream {ReadableStream<Uint8Array>} The stream to convert
39
+ * @returns {Promise<string>} The string content of stream
40
+ * @throws {TypeError} If stream is not a ReadableStream instance
41
+ */
42
+ declare function streamToString(stream: ReadableStream<Uint8Array>): Promise<string>;
43
+ /**
44
+ * Test wrapper for a function.
45
+ *
46
+ * ```ts
47
+ * import { FuncWarper } from '@faasjs/dev'
48
+ * import Func from '../demo.func.ts'
49
+ *
50
+ * const func = new FuncWarper(Func)
51
+ *
52
+ * expect(await func.handler()).toEqual('Hello, world')
53
+ * ```
54
+ */
55
+ declare class FuncWarper {
56
+ [key: string]: any;
57
+ readonly file: string;
58
+ readonly staging: string;
59
+ readonly logger: Logger;
60
+ readonly func: Func;
61
+ readonly config: Config;
62
+ readonly plugins: Plugin[];
63
+ private readonly _handler;
64
+ /**
65
+ * @param file {string} Full file path
66
+ * @param func {Func} A FaasJs function
67
+ * ```ts
68
+ * import { FuncWarper } from '@faasjs/dev'
69
+ *
70
+ * new FuncWarper(__dirname + '/../demo.func.ts')
71
+ * ```
72
+ */
73
+ constructor(initBy: Func);
74
+ mount(handler?: (func: FuncWarper) => Promise<void> | void): Promise<void>;
75
+ handler<TResult = any>(event?: any, context?: any): Promise<TResult>;
76
+ JSONhandler<TData = any>(body?: {
77
+ [key: string]: any;
78
+ }, options?: {
79
+ headers?: {
80
+ [key: string]: any;
81
+ };
82
+ cookie?: {
83
+ [key: string]: any;
84
+ };
85
+ session?: {
86
+ [key: string]: any;
87
+ };
88
+ }): Promise<{
89
+ statusCode: number;
90
+ headers: {
91
+ [key: string]: string;
92
+ };
93
+ cookie?: Record<string, any>;
94
+ session?: Record<string, any>;
95
+ body: any;
96
+ data?: TData;
97
+ error?: {
98
+ message: string;
99
+ };
100
+ }>;
101
+ }
102
+ /**
103
+ * A simple way to wrap a FaasJS function.
104
+ * @param initBy {Func} Full file path or a FaasJs function
105
+ *
106
+ * ```ts
107
+ * import { test } from '@faasjs/dev'
108
+ * import Func from '../demo.func.ts'
109
+ *
110
+ * const func = test(Func)
111
+ *
112
+ * expect(await func.handler()).toEqual('Hello, world')
113
+ * ```
114
+ */
115
+ declare function test(initBy: Func): FuncWarper;
116
+
117
+ type ViteFaasJsServerOptions = {
118
+ /** faas project root path, default is vite's root */
119
+ root: string;
120
+ /** faas server base path, default is vite's base */
121
+ base: string;
122
+ };
123
+ /**
124
+ * Create a Vite plugin that proxies POST requests to an in-process FaasJS server.
125
+ *
126
+ * It resolves project root/base from Vite config and strips `base` from request URL
127
+ * before forwarding to `@faasjs/server`.
128
+ */
129
+ declare function viteFaasJsServer(options?: Partial<ViteFaasJsServerOptions> & Record<string, unknown>): Plugin$1;
130
+
131
+ export { FuncWarper, type MountFaasKnexOptions, type ViteFaasJsServerOptions, createPgliteKnex, mountFaasKnex, runPgliteSql, runPgliteSqlFile, streamToString, test, unmountFaasKnex, viteFaasJsServer };
package/dist/index.mjs ADDED
@@ -0,0 +1,323 @@
1
+ import { readFileSync } from 'fs';
2
+ import { knex } from 'knex';
3
+ import PgliteDialect from 'knex-pglite';
4
+ import { brotliDecompressSync, gunzipSync, inflateSync } from 'zlib';
5
+ import { deepMerge } from '@faasjs/deep_merge';
6
+ import { Http } from '@faasjs/http';
7
+ import { loadConfig } from '@faasjs/load';
8
+ import { Logger } from '@faasjs/logger';
9
+ import * as func_star from '@faasjs/func';
10
+ import { join } from 'path';
11
+ import { Server } from '@faasjs/server';
12
+
13
+ var __defProp = Object.defineProperty;
14
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
15
+ var __getOwnPropNames = Object.getOwnPropertyNames;
16
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
17
+ var __export = (target, all) => {
18
+ for (var name in all)
19
+ __defProp(target, name, { get: all[name], enumerable: true });
20
+ };
21
+ var __copyProps = (to, from, except, desc) => {
22
+ if (from && typeof from === "object" || typeof from === "function") {
23
+ for (let key of __getOwnPropNames(from))
24
+ if (!__hasOwnProp.call(to, key) && key !== except)
25
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
26
+ }
27
+ return to;
28
+ };
29
+ var __reExport = (target, mod, secondTarget) => (__copyProps(target, mod, "default"), secondTarget);
30
+
31
+ // src/index.ts
32
+ var index_exports = {};
33
+ __export(index_exports, {
34
+ FuncWarper: () => FuncWarper,
35
+ createPgliteKnex: () => createPgliteKnex,
36
+ mountFaasKnex: () => mountFaasKnex,
37
+ runPgliteSql: () => runPgliteSql,
38
+ runPgliteSqlFile: () => runPgliteSqlFile,
39
+ streamToString: () => streamToString,
40
+ test: () => test,
41
+ unmountFaasKnex: () => unmountFaasKnex,
42
+ viteFaasJsServer: () => viteFaasJsServer
43
+ });
44
+ function createPgliteKnex(config = {}, connection = {}) {
45
+ return knex({
46
+ ...config,
47
+ client: PgliteDialect,
48
+ connection
49
+ });
50
+ }
51
+ function mountFaasKnex(db, options = {}) {
52
+ const globalWithFaasKnex = globalThis;
53
+ const name = options.name || "knex";
54
+ if (!globalWithFaasKnex.FaasJS_Knex) globalWithFaasKnex.FaasJS_Knex = {};
55
+ globalWithFaasKnex.FaasJS_Knex[name] = {
56
+ adapter: db,
57
+ query: db,
58
+ config: options.config || {}
59
+ };
60
+ }
61
+ function unmountFaasKnex(name = "knex") {
62
+ const globalWithFaasKnex = globalThis;
63
+ if (!globalWithFaasKnex.FaasJS_Knex) return;
64
+ delete globalWithFaasKnex.FaasJS_Knex[name];
65
+ }
66
+ async function runPgliteSql(db, sql) {
67
+ if (!sql.trim()) return;
68
+ await db.raw(sql);
69
+ }
70
+ async function runPgliteSqlFile(db, filePath, options = {}) {
71
+ const stripUuidOsspExtension = options.stripUuidOsspExtension !== false;
72
+ let sql = readFileSync(filePath, "utf8");
73
+ if (stripUuidOsspExtension)
74
+ sql = sql.replace(/CREATE EXTENSION IF NOT EXISTS "uuid-ossp";\s*/gi, "");
75
+ await runPgliteSql(db, sql);
76
+ }
77
+
78
+ // src/test.ts
79
+ var test_exports = {};
80
+ __export(test_exports, {
81
+ FuncWarper: () => FuncWarper,
82
+ streamToString: () => streamToString,
83
+ test: () => test
84
+ });
85
+ __reExport(test_exports, func_star);
86
+ async function streamToString(stream) {
87
+ if (!(stream instanceof ReadableStream))
88
+ throw new TypeError("stream must be a ReadableStream instance");
89
+ const reader = stream.getReader();
90
+ const chunks = [];
91
+ try {
92
+ while (true) {
93
+ const { done, value } = await reader.read();
94
+ if (done) break;
95
+ if (value) chunks.push(value);
96
+ }
97
+ } finally {
98
+ reader.releaseLock();
99
+ }
100
+ const decoder = new TextDecoder();
101
+ return decoder.decode(Buffer.concat(chunks.map((c) => Buffer.from(c))));
102
+ }
103
+ var FuncWarper = class {
104
+ file;
105
+ staging;
106
+ logger;
107
+ func;
108
+ config;
109
+ plugins;
110
+ _handler;
111
+ /**
112
+ * @param file {string} Full file path
113
+ * @param func {Func} A FaasJs function
114
+ * ```ts
115
+ * import { FuncWarper } from '@faasjs/dev'
116
+ *
117
+ * new FuncWarper(__dirname + '/../demo.func.ts')
118
+ * ```
119
+ */
120
+ constructor(initBy) {
121
+ this.staging = process.env.FaasEnv;
122
+ this.logger = new Logger("TestCase");
123
+ this.func = initBy.default ? initBy.default : initBy;
124
+ if (this.func.filename)
125
+ this.func.config = deepMerge(
126
+ loadConfig(process.cwd(), initBy.filename, this.staging, this.logger),
127
+ initBy.config
128
+ );
129
+ this.plugins = this.func.plugins || [];
130
+ for (const plugin of this.plugins) {
131
+ if (["handler", "config", "plugins", "logger", "mount"].includes(
132
+ plugin.type
133
+ ))
134
+ continue;
135
+ this[plugin.type] = plugin;
136
+ }
137
+ this._handler = this.func.export().handler;
138
+ }
139
+ async mount(handler) {
140
+ if (!this.func.mounted) await this.func.mount();
141
+ if (handler) await handler(this);
142
+ }
143
+ async handler(event = /* @__PURE__ */ Object.create(null), context = /* @__PURE__ */ Object.create(null)) {
144
+ await this.mount();
145
+ const response = await this._handler(event, context);
146
+ this.logger.debug("response: %j", response);
147
+ return response;
148
+ }
149
+ async JSONhandler(body, options = /* @__PURE__ */ Object.create(null)) {
150
+ await this.mount();
151
+ const headers = options.headers || /* @__PURE__ */ Object.create(null);
152
+ if (this.http && this.http instanceof Http) {
153
+ if (options.cookie)
154
+ for (const key in options.cookie)
155
+ this.http.cookie.write(key, options.cookie[key]);
156
+ if (options.session) {
157
+ for (const key in options.session)
158
+ this.http.session.write(key, options.session[key]);
159
+ this.http.session.update();
160
+ }
161
+ const cookie = this.http.cookie.headers()["Set-Cookie"]?.map((c) => c.split(";")[0]).join(";");
162
+ if (cookie)
163
+ if (headers.cookie) headers.cookie += `;${cookie}`;
164
+ else headers.cookie = cookie;
165
+ }
166
+ const response = await this._handler({
167
+ httpMethod: "POST",
168
+ headers: Object.assign({ "content-type": "application/json" }, headers),
169
+ body: typeof body === "string" ? body : JSON.stringify(body)
170
+ });
171
+ if (response?.body instanceof ReadableStream) {
172
+ let stream = response.body;
173
+ const encoding = response.headers?.["Content-Encoding"] || response.headers?.["content-encoding"];
174
+ if (encoding) {
175
+ const chunks = [];
176
+ const reader = stream.getReader();
177
+ try {
178
+ while (true) {
179
+ const { done, value } = await reader.read();
180
+ if (done) break;
181
+ if (value) chunks.push(value);
182
+ }
183
+ } catch (error) {
184
+ this.logger.error("Failed to read ReadableStream: %s", error);
185
+ response.body = JSON.stringify({
186
+ error: { message: error.message }
187
+ });
188
+ response.error = { message: error.message };
189
+ response.statusCode = 500;
190
+ reader.releaseLock();
191
+ return response;
192
+ }
193
+ reader.releaseLock();
194
+ const compressedBuffer = Buffer.concat(chunks);
195
+ try {
196
+ let decompressed;
197
+ if (encoding === "br") {
198
+ decompressed = brotliDecompressSync(compressedBuffer);
199
+ } else if (encoding === "gzip") {
200
+ decompressed = gunzipSync(compressedBuffer);
201
+ } else if (encoding === "deflate") {
202
+ decompressed = inflateSync(compressedBuffer);
203
+ } else {
204
+ throw new Error(`Unsupported encoding: ${encoding}`);
205
+ }
206
+ stream = new ReadableStream({
207
+ start(controller) {
208
+ controller.enqueue(new Uint8Array(decompressed));
209
+ controller.close();
210
+ }
211
+ });
212
+ } catch (error) {
213
+ this.logger.error("Failed to decompress: %s", error);
214
+ response.body = JSON.stringify({
215
+ error: { message: error.message }
216
+ });
217
+ response.error = { message: error.message };
218
+ response.statusCode = 500;
219
+ return response;
220
+ }
221
+ }
222
+ try {
223
+ response.body = await streamToString(stream);
224
+ } catch (error) {
225
+ this.logger.error("Failed to decode ReadableStream: %s", error);
226
+ response.body = JSON.stringify({
227
+ error: { message: error.message }
228
+ });
229
+ response.error = { message: error.message };
230
+ response.statusCode = 500;
231
+ }
232
+ }
233
+ if (response?.headers && response.body && response.headers["content-type"]?.includes("json")) {
234
+ const parsedBody = JSON.parse(response.body);
235
+ response.data = parsedBody.data;
236
+ response.error = parsedBody.error;
237
+ }
238
+ if (this.http) {
239
+ response.cookie = this.http.cookie.content;
240
+ response.session = this.http.session.content;
241
+ }
242
+ this.logger.debug("response: %j", response);
243
+ return response;
244
+ }
245
+ };
246
+ function test(initBy) {
247
+ const warper = new FuncWarper(initBy);
248
+ warper.mount = warper.mount.bind(warper);
249
+ warper.handler = warper.handler.bind(warper);
250
+ warper.JSONhandler = warper.JSONhandler.bind(warper);
251
+ return warper;
252
+ }
253
+
254
+ // src/index.ts
255
+ __reExport(index_exports, test_exports);
256
+ function normalizeBase(base) {
257
+ const normalized = base.startsWith("/") ? base : `/${base}`;
258
+ if (normalized === "/") return "/";
259
+ return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
260
+ }
261
+ function stripBase(url, base) {
262
+ if (base === "/") return url;
263
+ const queryIndex = url.indexOf("?");
264
+ const pathname = queryIndex >= 0 ? url.slice(0, queryIndex) : url;
265
+ const search = queryIndex >= 0 ? url.slice(queryIndex) : "";
266
+ if (pathname === base) return `/${search}`;
267
+ if (pathname.startsWith(`${base}/`))
268
+ return `${pathname.slice(base.length)}${search}`;
269
+ return url;
270
+ }
271
+ function viteFaasJsServer(options = {}) {
272
+ let config;
273
+ let server = null;
274
+ const logger = new Logger("FaasJs:Vite");
275
+ return {
276
+ name: "vite:faasjs",
277
+ enforce: "pre",
278
+ configResolved(resolvedConfig) {
279
+ const root = options.root || resolvedConfig.root;
280
+ const base = normalizeBase(options.base || resolvedConfig.base);
281
+ config = {
282
+ root,
283
+ base
284
+ };
285
+ },
286
+ configureServer: async ({ middlewares }) => {
287
+ if (process.env.VITEST) {
288
+ logger.debug("Skipping faas server in vitest environment");
289
+ return;
290
+ }
291
+ if (!config) throw new Error("viteFaasJsServer: config is not resolved");
292
+ server = new Server(join(config.root, "src"));
293
+ middlewares.use(async (req, res, next) => {
294
+ if (!req.url || req.method !== "POST" || !server) return next();
295
+ const originalUrl = req.url;
296
+ const strippedUrl = stripBase(req.url, config.base);
297
+ req.url = strippedUrl;
298
+ try {
299
+ logger.debug(`Request ${req.url}`);
300
+ await server.handle(req, res, {
301
+ requestedAt: Date.now()
302
+ });
303
+ } catch (error) {
304
+ logger.error(error);
305
+ if (!res.headersSent && !res.writableEnded) {
306
+ res.writeHead(500, { "Content-Type": "application/json" });
307
+ res.write(
308
+ JSON.stringify({
309
+ error: { message: "Internal Server Error" }
310
+ })
311
+ );
312
+ res.end();
313
+ }
314
+ } finally {
315
+ req.url = originalUrl;
316
+ }
317
+ if (!res.writableEnded) next();
318
+ });
319
+ }
320
+ };
321
+ }
322
+
323
+ export { FuncWarper, createPgliteKnex, mountFaasKnex, runPgliteSql, runPgliteSqlFile, streamToString, test, unmountFaasKnex, viteFaasJsServer };
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@faasjs/dev",
3
+ "version": "v8.0.0-beta.5",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.mjs",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.mjs",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "homepage": "https://faasjs.com/doc/dev",
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/faasjs/faasjs.git",
20
+ "directory": "packages/dev"
21
+ },
22
+ "bugs": {
23
+ "url": "https://github.com/faasjs/faasjs/issues"
24
+ },
25
+ "funding": "https://github.com/sponsors/faasjs",
26
+ "scripts": {
27
+ "build": "tsup-node src/index.ts --config ../../tsup.config.ts"
28
+ },
29
+ "files": [
30
+ "dist"
31
+ ],
32
+ "peerDependencies": {
33
+ "@faasjs/deep_merge": ">=v8.0.0-beta.5",
34
+ "@faasjs/func": ">=v8.0.0-beta.5",
35
+ "@faasjs/http": ">=v8.0.0-beta.5",
36
+ "@faasjs/server": ">=v8.0.0-beta.5",
37
+ "@faasjs/knex": ">=v8.0.0-beta.5",
38
+ "@faasjs/load": ">=v8.0.0-beta.5",
39
+ "@faasjs/logger": ">=v8.0.0-beta.5",
40
+ "knex": "*",
41
+ "vite": "*",
42
+ "knex-pglite": "*"
43
+ },
44
+ "devDependencies": {
45
+ "@faasjs/deep_merge": ">=v8.0.0-beta.5",
46
+ "@faasjs/func": ">=v8.0.0-beta.5",
47
+ "@faasjs/http": ">=v8.0.0-beta.5",
48
+ "@faasjs/server": ">=v8.0.0-beta.5",
49
+ "@faasjs/knex": ">=v8.0.0-beta.5",
50
+ "@faasjs/load": ">=v8.0.0-beta.5",
51
+ "@faasjs/logger": ">=v8.0.0-beta.5",
52
+ "knex": "*",
53
+ "vite": "*",
54
+ "knex-pglite": "*"
55
+ },
56
+ "engines": {
57
+ "node": ">=24.0.0",
58
+ "npm": ">=11.0.0"
59
+ }
60
+ }