@faasjs/dev 8.0.0-beta.5 → 8.0.0-beta.7

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/dist/index.mjs CHANGED
@@ -1,323 +1,287 @@
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';
1
+ import { n as __reExport, t as __exportAll } from "./chunk-CtajNgzt.mjs";
2
+ import { n as isTypegenSourceFile, r as resolveServerConfig, t as generateFaasTypes } from "./typegen-D5s91_xL.mjs";
3
+ import { brotliDecompressSync, gunzipSync, inflateSync } from "node:zlib";
4
+ import { Http } from "@faasjs/http";
5
+ import { Logger } from "@faasjs/logger";
6
+ import { deepMerge, loadConfig, streamToObject, streamToString, streamToText } from "@faasjs/node-utils";
7
+ import { join } from "node:path";
8
+ import { Server } from "@faasjs/server";
12
9
 
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);
10
+ export * from "@faasjs/func"
30
11
 
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
- }
12
+ //#region src/test.ts
13
+ /**
14
+ * Test wrapper for a function.
15
+ *
16
+ * ```ts
17
+ * import { FuncWarper } from '@faasjs/dev'
18
+ * import Func from '../demo.func.ts'
19
+ *
20
+ * const func = new FuncWarper(Func)
21
+ *
22
+ * expect(await func.handler()).toEqual('Hello, world')
23
+ * ```
24
+ */
103
25
  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
- }
26
+ file;
27
+ staging;
28
+ logger;
29
+ func;
30
+ config;
31
+ plugins;
32
+ _handler;
33
+ /**
34
+ * @param initBy {Func} A FaasJS function
35
+ * ```ts
36
+ * import { FuncWarper } from '@faasjs/dev'
37
+ *
38
+ * new FuncWarper(__dirname + '/../demo.func.ts')
39
+ * ```
40
+ */
41
+ constructor(initBy) {
42
+ this.staging = process.env.FaasEnv ?? "default";
43
+ this.logger = new Logger("TestCase");
44
+ this.func = initBy.default ? initBy.default : initBy;
45
+ if (this.func.filename) this.func.config = deepMerge(loadConfig(process.cwd(), this.func.filename, this.staging, this.logger), this.func.config);
46
+ this.file = this.func.filename || "";
47
+ this.config = this.func.config;
48
+ this.plugins = this.func.plugins || [];
49
+ for (const plugin of this.plugins) {
50
+ if ([
51
+ "handler",
52
+ "config",
53
+ "plugins",
54
+ "logger",
55
+ "mount"
56
+ ].includes(plugin.type)) continue;
57
+ this[plugin.type] = plugin;
58
+ }
59
+ this._handler = this.func.export().handler;
60
+ }
61
+ async mount(handler) {
62
+ if (!this.func.mounted) await this.func.mount();
63
+ if (handler) await handler(this);
64
+ }
65
+ async handler(event = Object.create(null), context = Object.create(null)) {
66
+ await this.mount();
67
+ const response = await this._handler(event, context);
68
+ this.logger.debug("response: %j", response);
69
+ return response;
70
+ }
71
+ async JSONhandler(body, options = Object.create(null)) {
72
+ await this.mount();
73
+ const headers = options.headers || Object.create(null);
74
+ if (this.http && this.http instanceof Http) {
75
+ if (options.cookie) for (const key in options.cookie) this.http.cookie.write(key, options.cookie[key]);
76
+ if (options.session) {
77
+ for (const key in options.session) this.http.session.write(key, options.session[key]);
78
+ this.http.session.update();
79
+ }
80
+ const cookie = this.http.cookie.headers()["Set-Cookie"]?.map((c) => c.split(";")[0]).join(";");
81
+ if (cookie) if (headers.cookie) headers.cookie += `;${cookie}`;
82
+ else headers.cookie = cookie;
83
+ }
84
+ const response = await this._handler({
85
+ httpMethod: "POST",
86
+ headers: Object.assign({ "content-type": "application/json" }, headers),
87
+ body: typeof body === "string" ? body : JSON.stringify(body)
88
+ });
89
+ if (response?.body instanceof ReadableStream) {
90
+ let stream = response.body;
91
+ const encoding = response.headers?.["Content-Encoding"] || response.headers?.["content-encoding"];
92
+ if (encoding) {
93
+ const chunks = [];
94
+ const reader = stream.getReader();
95
+ try {
96
+ while (true) {
97
+ const { done, value } = await reader.read();
98
+ if (done) break;
99
+ if (value) chunks.push(value);
100
+ }
101
+ } catch (error) {
102
+ this.logger.error("Failed to read ReadableStream: %s", error);
103
+ response.body = JSON.stringify({ error: { message: error.message } });
104
+ response.error = { message: error.message };
105
+ response.statusCode = 500;
106
+ reader.releaseLock();
107
+ return response;
108
+ }
109
+ reader.releaseLock();
110
+ const compressedBuffer = Buffer.concat(chunks);
111
+ try {
112
+ let decompressed;
113
+ if (encoding === "br") decompressed = brotliDecompressSync(compressedBuffer);
114
+ else if (encoding === "gzip") decompressed = gunzipSync(compressedBuffer);
115
+ else if (encoding === "deflate") decompressed = inflateSync(compressedBuffer);
116
+ else throw new Error(`Unsupported encoding: ${encoding}`);
117
+ stream = new ReadableStream({ start(controller) {
118
+ controller.enqueue(new Uint8Array(decompressed));
119
+ controller.close();
120
+ } });
121
+ } catch (error) {
122
+ this.logger.error("Failed to decompress: %s", error);
123
+ response.body = JSON.stringify({ error: { message: error.message } });
124
+ response.error = { message: error.message };
125
+ response.statusCode = 500;
126
+ return response;
127
+ }
128
+ }
129
+ try {
130
+ response.body = await streamToText(stream);
131
+ } catch (error) {
132
+ this.logger.error("Failed to decode ReadableStream: %s", error);
133
+ response.body = JSON.stringify({ error: { message: error.message } });
134
+ response.error = { message: error.message };
135
+ response.statusCode = 500;
136
+ }
137
+ }
138
+ if (response?.headers && response.body && response.headers["content-type"]?.includes("json")) {
139
+ const parsedBody = JSON.parse(response.body);
140
+ response.data = parsedBody.data;
141
+ response.error = parsedBody.error;
142
+ }
143
+ if (this.http) {
144
+ response.cookie = this.http.cookie.content;
145
+ response.session = this.http.session.content;
146
+ }
147
+ this.logger.debug("response: %j", response);
148
+ return response;
149
+ }
245
150
  };
151
+ /**
152
+ * A simple way to wrap a FaasJS function.
153
+ * @param initBy {Func} Full file path or a FaasJs function
154
+ *
155
+ * ```ts
156
+ * import { test } from '@faasjs/dev'
157
+ * import Func from '../demo.func.ts'
158
+ *
159
+ * const func = test(Func)
160
+ *
161
+ * expect(await func.handler()).toEqual('Hello, world')
162
+ * ```
163
+ */
246
164
  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;
165
+ const warper = new FuncWarper(initBy);
166
+ warper.mount = warper.mount.bind(warper);
167
+ warper.handler = warper.handler.bind(warper);
168
+ warper.JSONhandler = warper.JSONhandler.bind(warper);
169
+ return warper;
252
170
  }
253
171
 
254
- // src/index.ts
255
- __reExport(index_exports, test_exports);
172
+ //#endregion
173
+ //#region src/vite.ts
174
+ const TYPEGEN_DEBOUNCE = 120;
256
175
  function normalizeBase(base) {
257
- const normalized = base.startsWith("/") ? base : `/${base}`;
258
- if (normalized === "/") return "/";
259
- return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
176
+ const normalized = base.startsWith("/") ? base : `/${base}`;
177
+ if (normalized === "/") return "/";
178
+ return normalized.endsWith("/") ? normalized.slice(0, -1) : normalized;
260
179
  }
261
180
  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;
181
+ if (base === "/") return url;
182
+ const queryIndex = url.indexOf("?");
183
+ const pathname = queryIndex >= 0 ? url.slice(0, queryIndex) : url;
184
+ const search = queryIndex >= 0 ? url.slice(queryIndex) : "";
185
+ if (pathname === base) return `/${search}`;
186
+ if (pathname.startsWith(`${base}/`)) return `${pathname.slice(base.length)}${search}`;
187
+ return url;
270
188
  }
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
- };
189
+ /**
190
+ * Create a Vite plugin that proxies POST requests to an in-process FaasJS server.
191
+ *
192
+ * It resolves server root/base from `src/faas.yaml` and strips `base` from
193
+ * request URL before forwarding to `@faasjs/server`.
194
+ */
195
+ function viteFaasJsServer() {
196
+ let config;
197
+ let server = null;
198
+ const logger = new Logger("FaasJs:Vite");
199
+ return {
200
+ name: "vite:faasjs",
201
+ enforce: "pre",
202
+ configResolved(resolvedConfig) {
203
+ const serverConfig = resolveServerConfig(resolvedConfig.root, logger, resolvedConfig.base);
204
+ config = {
205
+ root: serverConfig.root,
206
+ base: normalizeBase(serverConfig.base)
207
+ };
208
+ },
209
+ configureServer: async ({ middlewares, watcher }) => {
210
+ if (process.env.VITEST) {
211
+ logger.debug("Skipping faas server in vitest environment");
212
+ return;
213
+ }
214
+ if (!config) throw new Error("viteFaasJsServer: config is not resolved");
215
+ server = new Server(join(config.root, "src"));
216
+ const runTypegen = async () => {
217
+ try {
218
+ const result = await generateFaasTypes({ root: config.root });
219
+ logger.debug("[faas-types] %s %s (%i routes)", result.changed ? "generated" : "up-to-date", result.output, result.routeCount);
220
+ } catch (error) {
221
+ logger.error("[faas-types] %s", error.message);
222
+ }
223
+ };
224
+ let timer = null;
225
+ let runningTypegen = false;
226
+ let pendingTypegen = false;
227
+ const flushTypegen = async () => {
228
+ if (runningTypegen || !pendingTypegen) return;
229
+ pendingTypegen = false;
230
+ runningTypegen = true;
231
+ try {
232
+ await runTypegen();
233
+ } finally {
234
+ runningTypegen = false;
235
+ if (pendingTypegen) flushTypegen();
236
+ }
237
+ };
238
+ const scheduleTypegen = () => {
239
+ pendingTypegen = true;
240
+ if (timer) clearTimeout(timer);
241
+ timer = setTimeout(() => {
242
+ flushTypegen();
243
+ }, TYPEGEN_DEBOUNCE);
244
+ };
245
+ await runTypegen();
246
+ watcher.on("all", (_eventName, filePath) => {
247
+ if (!isTypegenSourceFile(filePath)) return;
248
+ scheduleTypegen();
249
+ });
250
+ middlewares.use(async (req, res, next) => {
251
+ if (!req.url || req.method !== "POST" || !server) return next();
252
+ const originalUrl = req.url;
253
+ req.url = stripBase(req.url, config.base);
254
+ try {
255
+ logger.debug(`Request ${req.url}`);
256
+ await server.handle(req, res, { requestedAt: Date.now() });
257
+ } catch (error) {
258
+ logger.error(error);
259
+ if (!res.headersSent && !res.writableEnded) {
260
+ res.writeHead(500, { "Content-Type": "application/json" });
261
+ res.write(JSON.stringify({ error: { message: "Internal Server Error" } }));
262
+ res.end();
263
+ }
264
+ } finally {
265
+ req.url = originalUrl;
266
+ }
267
+ if (!res.writableEnded) next();
268
+ });
269
+ }
270
+ };
321
271
  }
322
272
 
323
- export { FuncWarper, createPgliteKnex, mountFaasKnex, runPgliteSql, runPgliteSqlFile, streamToString, test, unmountFaasKnex, viteFaasJsServer };
273
+ //#endregion
274
+ //#region src/index.ts
275
+ var src_exports = /* @__PURE__ */ __exportAll({
276
+ FuncWarper: () => FuncWarper,
277
+ generateFaasTypes: () => generateFaasTypes,
278
+ isTypegenSourceFile: () => isTypegenSourceFile,
279
+ streamToObject: () => streamToObject,
280
+ streamToString: () => streamToString,
281
+ streamToText: () => streamToText,
282
+ test: () => test,
283
+ viteFaasJsServer: () => viteFaasJsServer
284
+ });
285
+
286
+ //#endregion
287
+ export { FuncWarper, generateFaasTypes, isTypegenSourceFile, streamToObject, streamToString, streamToText, test, viteFaasJsServer };