bun-assemblyscript 0.1.0

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/src/plugin.ts ADDED
@@ -0,0 +1,189 @@
1
+ import type { BunPlugin } from "bun";
2
+ import { compile, type CompilerOptions } from "./compiler";
3
+ import { instantiate } from "./instantiator";
4
+ import { parseASExports, type ASExport } from "./typegen/parser";
5
+ import { generateDts } from "./typegen/generator";
6
+ import { join } from "path";
7
+ import { existsSync } from "fs";
8
+
9
+ export interface PluginOptions {
10
+ compilerOverrides?: CompilerOptions;
11
+ }
12
+
13
+ function isBuildMode(build: { config?: { target?: string } }): boolean {
14
+ return (
15
+ build.config !== undefined &&
16
+ typeof build.config.target === "string" &&
17
+ build.config.target.length > 0 &&
18
+ process.env.NODE_ENV === "production"
19
+ );
20
+ }
21
+
22
+ function isComplexType(t: string): boolean {
23
+ const trim = t.trim();
24
+ return trim === "string" || trim === "String" || trim.includes("Array") || trim.includes("Map");
25
+ }
26
+
27
+ function hasComplexExports(exports: ASExport[]): boolean {
28
+ for (const exp of exports) {
29
+ if (exp.kind === "class") return true;
30
+ if (exp.type && isComplexType(exp.type)) return true;
31
+ if (exp.params && exp.params.some((p) => isComplexType(p.type))) return true;
32
+ if (exp.returnType && isComplexType(exp.returnType)) return true;
33
+ if (exp.methods && hasComplexExports(exp.methods)) return true;
34
+ }
35
+ return false;
36
+ }
37
+
38
+ function generateInlineModule(exportNames: string[], wasmBase64: string): string {
39
+ const lines: string[] = [
40
+ `// Auto-generated by bun-plugin-assemblyscript (inline)`,
41
+ `const __b64 = ${JSON.stringify(wasmBase64)};`,
42
+ `const __bin = Uint8Array.from(atob(__b64), (c) => c.charCodeAt(0));`,
43
+ `const __memory = new WebAssembly.Memory({ initial: 1 });`,
44
+ `function __readStr(mem, ptr) {`,
45
+ ` if (ptr === 0) return "";`,
46
+ ` const dv = new DataView(mem.buffer);`,
47
+ ` const len = dv.getInt32(ptr - 4, true) >>> 1;`,
48
+ ` if (len <= 0) return "";`,
49
+ ` return String.fromCharCode(...new Uint16Array(mem.buffer, ptr, len));`,
50
+ `}`,
51
+ `const __imports = {`,
52
+ ` env: {`,
53
+ ` memory: __memory,`,
54
+ ` abort(msg, file, line, col) {`,
55
+ ` const m = __readStr(__memory, msg);`,
56
+ ` const f = __readStr(__memory, file);`,
57
+ ` throw new Error(\`AbortError: \${m || "(no)"} — \${f}:\${line}:\${col}\`);`,
58
+ ` }`,
59
+ ` }`,
60
+ `};`,
61
+ `const { instance: __inst } = await WebAssembly.instantiate(__bin, __imports);`,
62
+ ];
63
+ for (const name of exportNames) {
64
+ lines.push(`export const ${name} = __inst.exports.${name};`);
65
+ }
66
+ return lines.join("\n") + "\n";
67
+ }
68
+
69
+ function generateFileModule(exportNames: string[], wasmUrl: string): string {
70
+ const lines: string[] = [
71
+ `// Auto-generated by bun-plugin-assemblyscript (file)`,
72
+ `const __url = ${JSON.stringify(wasmUrl)};`,
73
+ `const __res = await fetch(__url);`,
74
+ `const __bin = new Uint8Array(await __res.arrayBuffer());`,
75
+ `const __memory = new WebAssembly.Memory({ initial: 1 });`,
76
+ `function __readStr(mem, ptr) {`,
77
+ ` if (ptr === 0) return "";`,
78
+ ` const dv = new DataView(mem.buffer);`,
79
+ ` const len = dv.getInt32(ptr - 4, true) >>> 1;`,
80
+ ` if (len <= 0) return "";`,
81
+ ` return String.fromCharCode(...new Uint16Array(mem.buffer, ptr, len));`,
82
+ `}`,
83
+ `const __imports = {`,
84
+ ` env: {`,
85
+ ` memory: __memory,`,
86
+ ` abort(msg, file, line, col) {`,
87
+ ` const m = __readStr(__memory, msg);`,
88
+ ` const f = __readStr(__memory, file);`,
89
+ ` throw new Error(\`AbortError: \${m || "(no)"} — \${f}:\${line}:\${col}\`);`,
90
+ ` }`,
91
+ ` }`,
92
+ `};`,
93
+ `const { instance: __inst } = await WebAssembly.instantiate(__bin, __imports);`,
94
+ ];
95
+ for (const name of exportNames) {
96
+ lines.push(`export const ${name} = __inst.exports.${name};`);
97
+ }
98
+ return lines.join("\n") + "\n";
99
+ }
100
+
101
+ export function assemblyScriptPlugin(options: PluginOptions = {}): BunPlugin {
102
+ return {
103
+ name: "bun-plugin-assemblyscript",
104
+ async setup(build) {
105
+ let embedMode = "auto";
106
+ try {
107
+ if (existsSync("bunfig.toml")) {
108
+ const config = await Bun.file("bunfig.toml").text();
109
+ const match = config.match(/embedMode\s*=\s*(?:"|')([^"']+)(?:"|')/);
110
+ if (match) embedMode = match[1];
111
+ }
112
+ } catch (e) {}
113
+
114
+ // Build context
115
+ const isProd = isBuildMode(build) || process.env.NODE_ENV === "production" || build.config?.minify;
116
+
117
+ build.onLoad({ filter: /\.as$/ }, async (args) => {
118
+ let parsedExports: ASExport[] = [];
119
+ let hasComplex = false;
120
+ try {
121
+ const source = await Bun.file(args.path).text();
122
+ parsedExports = parseASExports(source);
123
+ await generateDts(parsedExports, args.path);
124
+ hasComplex = hasComplexExports(parsedExports);
125
+ } catch (e) {
126
+ console.warn("[bun-assemblyscript] Génération de types échouée :", e);
127
+ }
128
+
129
+ const compilerOptions: CompilerOptions = {
130
+ optimizeLevel: isProd ? 3 : 0,
131
+ shrinkLevel: isProd ? 2 : 0,
132
+ runtime: hasComplex ? "incremental" : "stub",
133
+ sourceMap: !isProd,
134
+ debug: !isProd,
135
+ ...options.compilerOverrides,
136
+ };
137
+
138
+ const result = await compile(args.path, compilerOptions);
139
+
140
+ if (!result.success || !result.wasmBytes) {
141
+ return {
142
+ contents: "",
143
+ errors: result.errors.map((text) => ({ text })),
144
+ };
145
+ }
146
+
147
+ // Ecrire le sourcemap dans le cache à côté du .wasm si non prod
148
+ if (!isProd && result.sourceMapBytes) {
149
+ const cacheDir = join(process.cwd(), ".cache", "bun-as", require("path").basename(args.path));
150
+ const currentHash = require("crypto").createHash("sha256").update(await Bun.file(args.path).text()).digest("hex");
151
+ const mapPath = join(cacheDir, currentHash + ".wasm.map");
152
+ try {
153
+ await require("fs/promises").mkdir(cacheDir, { recursive: true });
154
+ await Bun.write(mapPath, result.sourceMapBytes);
155
+ } catch (e) {}
156
+ }
157
+
158
+ // Stratégie d'embedding
159
+ let finalMode = embedMode;
160
+ if (finalMode === "auto") {
161
+ finalMode = result.wasmBytes.length < 100000 ? "inline" : "file";
162
+ }
163
+
164
+ const exportsMap = await instantiate(result.wasmBytes);
165
+ const exportNames = Object.keys(exportsMap).filter(
166
+ (k) => k !== "__data_end" && k !== "__heap_base"
167
+ );
168
+
169
+ if (finalMode === "inline") {
170
+ const b64 = Buffer.from(result.wasmBytes).toString("base64");
171
+ return { contents: generateInlineModule(exportNames, b64), loader: "js" };
172
+ } else {
173
+ // File mode
174
+ const outdir = build.config?.outdir || "./dist";
175
+ const fileName = "module-" + Bun.hash(result.wasmBytes) + ".wasm";
176
+ const outPath = join(process.cwd(), outdir, fileName);
177
+
178
+ try {
179
+ await require("fs/promises").mkdir(join(process.cwd(), outdir), { recursive: true });
180
+ await Bun.write(outPath, result.wasmBytes);
181
+ } catch(e) {}
182
+
183
+ const wasmUrl = "./" + fileName;
184
+ return { contents: generateFileModule(exportNames, wasmUrl), loader: "js" };
185
+ }
186
+ });
187
+ },
188
+ };
189
+ }
@@ -0,0 +1,62 @@
1
+ import { mapType } from "./typemap";
2
+ import { type ASExport } from "./parser";
3
+
4
+ /**
5
+ * Génère le contenu d'un fichier .d.ts à partir d'un tableau d'exports AS.
6
+ * Si le contenu (hash) n'a pas changé, on n'écrase pas le fichier.
7
+ *
8
+ * @param exports Liste des exports parsés
9
+ * @param sourcePath Chemin absolu vers le fichier `.as` d'origine
10
+ */
11
+ export async function generateDts(
12
+ exports: ASExport[],
13
+ sourcePath: string
14
+ ): Promise<void> {
15
+ const lines: string[] = [
16
+ "// auto-generated by bun-plugin-assemblyscript — do not edit",
17
+ "",
18
+ ];
19
+
20
+ for (const exp of exports) {
21
+ if (exp.kind === "const") {
22
+ lines.push(`export const ${exp.name}: ${mapType(exp.type!)};`);
23
+ } else if (exp.kind === "function") {
24
+ const params = exp
25
+ .params!.map((p) => `${p.name}: ${mapType(p.type)}`)
26
+ .join(", ");
27
+ lines.push(
28
+ `export function ${exp.name}(${params}): ${mapType(exp.returnType!)};`
29
+ );
30
+ } else if (exp.kind === "class") {
31
+ lines.push(`export class ${exp.name} {`);
32
+ if (exp.methods) {
33
+ for (const m of exp.methods) {
34
+ const params = m
35
+ .params!.map((p) => `${p.name}: ${mapType(p.type)}`)
36
+ .join(", ");
37
+ lines.push(
38
+ ` ${m.name}(${params}): ${mapType(m.returnType!)};`
39
+ );
40
+ }
41
+ }
42
+ lines.push(`}`);
43
+ }
44
+ }
45
+
46
+ const dtsContent = lines.join("\n") + "\n";
47
+ const dtsPath = sourcePath + ".d.ts";
48
+ const newHash = Bun.hash(dtsContent);
49
+
50
+ try {
51
+ const existingContent = await Bun.file(dtsPath).text();
52
+ const oldHash = Bun.hash(existingContent);
53
+ if (newHash === oldHash) {
54
+ // Pas de modification structurelle de la signature, on ne fait rien
55
+ return;
56
+ }
57
+ } catch (err) {
58
+ // Fichier inexistant, on procède à l'écriture
59
+ }
60
+
61
+ await Bun.write(dtsPath, dtsContent);
62
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Types globaux AssemblyScript (primitives)
3
+ * Cela permet à l'éditeur de reconnaître i32, f64, etc. dans les fichiers .as
4
+ * sans entrer en conflit avec les types TypeScript standards (String, Array).
5
+ */
6
+ declare type i8 = number;
7
+ declare type i16 = number;
8
+ declare type i32 = number;
9
+ declare type i64 = bigint;
10
+ declare type isize = number;
11
+ declare type u8 = number;
12
+ declare type u16 = number;
13
+ declare type u32 = number;
14
+ declare type u64 = bigint;
15
+ declare type usize = number;
16
+ declare type f32 = number;
17
+ declare type f64 = number;
18
+ declare type bool = boolean;
19
+ declare type v128 = never;
20
+ declare type anyref = any;
21
+ declare type externref = any;
22
+
23
+ declare module "*.as" {
24
+ const exports: Record<string, unknown>;
25
+ export default exports;
26
+ }
@@ -0,0 +1,118 @@
1
+ export type ASExportKind = "function" | "const" | "class";
2
+
3
+ export interface ASParam {
4
+ name: string;
5
+ type: string;
6
+ }
7
+
8
+ export interface ASExport {
9
+ name: string;
10
+ kind: ASExportKind;
11
+ params?: ASParam[];
12
+ returnType?: string;
13
+ type?: string; // For constants
14
+ methods?: ASExport[]; // For class methods
15
+ }
16
+
17
+ function extractClassBody(text: string, startIndex: number): string {
18
+ let depth = 0;
19
+ let inClass = false;
20
+ let bodyStart = -1;
21
+ const length = text.length;
22
+
23
+ for (let i = startIndex; i < length; i++) {
24
+ const char = text[i];
25
+ if (char === "{") {
26
+ if (depth === 0) {
27
+ inClass = true;
28
+ bodyStart = i + 1;
29
+ }
30
+ depth++;
31
+ } else if (char === "}") {
32
+ depth--;
33
+ if (depth === 0 && inClass) {
34
+ return text.substring(bodyStart, i);
35
+ }
36
+ }
37
+ }
38
+ return "";
39
+ }
40
+
41
+ /**
42
+ * Parse un fichier source AssemblyScript (.as) et extrait les signatures publiques.
43
+ * Gère "export function", "export const" et "export class".
44
+ */
45
+ export function parseASExports(source: string): ASExport[] {
46
+ // Nettoyer les commentaires ( /* ... */ et // ... )
47
+ const text = source.replace(/\/\*[\s\S]*?\*\/|\/\/.*/g, "");
48
+ const exports: ASExport[] = [];
49
+
50
+ // Parsing des fonctions (hors des classes)
51
+ const fnRegex = /export\s+function\s+([a-zA-Z0-9_]+)\s*\(([^)]*)\)\s*:\s*([^;{]+)/g;
52
+ let match;
53
+ while ((match = fnRegex.exec(text)) !== null) {
54
+ const name = match[1];
55
+ const paramsStr = match[2];
56
+ const returnType = match[3].trim();
57
+
58
+ const params: ASParam[] = paramsStr
59
+ .split(",")
60
+ .filter((p) => p.trim())
61
+ .map((p) => {
62
+ const parts = p.split(":");
63
+ return {
64
+ name: parts[0].trim(),
65
+ type: parts.length > 1 ? parts[1].trim() : "unknown",
66
+ };
67
+ });
68
+
69
+ exports.push({ kind: "function", name, params, returnType });
70
+ }
71
+
72
+ // Parsing des constantes (export const)
73
+ const constRegex = /export\s+const\s+([a-zA-Z0-9_]+)\s*:\s*([^=;]+)/g;
74
+ while ((match = constRegex.exec(text)) !== null) {
75
+ exports.push({ kind: "const", name: match[1], type: match[2].trim() });
76
+ }
77
+
78
+ // Parsing des classes (export class)
79
+ const classRegex = /export\s+class\s+([a-zA-Z0-9_]+)/g;
80
+ while ((match = classRegex.exec(text)) !== null) {
81
+ const className = match[1];
82
+ const body = extractClassBody(text, match.index);
83
+
84
+ const methods: ASExport[] = [];
85
+ // Méthodes publiques de classe
86
+ const methodRegex = /(?:public\s+)?([a-zA-Z0-9_]+)\s*\(([^)]*)\)\s*:\s*([^;{]+)/g;
87
+ let mMatch;
88
+ while ((mMatch = methodRegex.exec(body)) !== null) {
89
+ const mName = mMatch[1];
90
+ if (mName === "constructor") continue;
91
+
92
+ const mParamsStr = mMatch[2];
93
+ const mReturnType = mMatch[3].trim();
94
+
95
+ const mParams: ASParam[] = mParamsStr
96
+ .split(",")
97
+ .filter((p) => p.trim())
98
+ .map((p) => {
99
+ const parts = p.split(":");
100
+ return {
101
+ name: parts[0].trim(),
102
+ type: parts.length > 1 ? parts[1].trim() : "unknown",
103
+ };
104
+ });
105
+
106
+ methods.push({
107
+ kind: "function",
108
+ name: mName,
109
+ params: mParams,
110
+ returnType: mReturnType,
111
+ });
112
+ }
113
+
114
+ exports.push({ kind: "class", name: className, methods });
115
+ }
116
+
117
+ return exports;
118
+ }
@@ -0,0 +1,51 @@
1
+ /**
2
+ * Mappe un type AssemblyScript vers un type de définition TypeScript (.d.ts).
3
+ */
4
+ export function mapType(asType: string): string {
5
+ const t = asType.trim();
6
+
7
+ // Primitives exactes
8
+ switch (t) {
9
+ case "i8":
10
+ case "i16":
11
+ case "i32":
12
+ case "i64":
13
+ case "u8":
14
+ case "u16":
15
+ case "u32":
16
+ case "u64":
17
+ case "f32":
18
+ case "f64":
19
+ case "isize":
20
+ case "usize":
21
+ return "number";
22
+
23
+ case "bool":
24
+ return "boolean";
25
+
26
+ case "string":
27
+ case "String":
28
+ return "string";
29
+
30
+ case "void":
31
+ return "void";
32
+ }
33
+
34
+ // Match des tableaux
35
+ const arrayMatch = t.match(/^(?:Static)?Array<(.+)>$/);
36
+ if (arrayMatch) {
37
+ return `${mapType(arrayMatch[1])}[]`;
38
+ }
39
+
40
+ // Match des Maps
41
+ const mapMatch = t.match(/^Map<(.+?)\s*,\s*(.+)>$/);
42
+ if (mapMatch) {
43
+ return `Map<${mapType(mapMatch[1])}, ${mapType(mapMatch[2])}>`;
44
+ }
45
+
46
+ // Fallback si type inconnu
47
+ console.warn(
48
+ `[bun-assemblyscript] Warning : Type AssemblyScript non reconnu '${t}'. Fallback sur 'unknown'.`
49
+ );
50
+ return "unknown";
51
+ }
@@ -0,0 +1,117 @@
1
+ import { Glob } from "bun";
2
+ import { watch } from "fs";
3
+ import { join, basename } from "path";
4
+ import { compile } from "../compiler";
5
+ import { get, set, computeHash, invalidate } from "../cache";
6
+ import { parseASExports } from "../typegen/parser";
7
+ import { generateDts } from "../typegen/generator";
8
+ import { logSuccess, logError, logStart } from "./logger";
9
+
10
+ // Conserve les watchers et les hashes en mémoire vive
11
+ const hashes = new Map<string, string>();
12
+ const watchers = new Map<string, any>();
13
+
14
+ async function processFile(filePath: string, isInitial: boolean, isProd = false) {
15
+ try {
16
+ const text = await Bun.file(filePath).text();
17
+ const hash = await computeHash(text);
18
+
19
+ // Skip si strictement rien n'a changé en mémoire
20
+ if (hashes.get(filePath) === hash) {
21
+ if (isInitial) {
22
+ // En initialisation, vérifier si le cache disque existe vraiment
23
+ const cached = await get(filePath, hash);
24
+ if (cached) return;
25
+ } else {
26
+ return;
27
+ }
28
+ }
29
+
30
+ hashes.set(filePath, hash);
31
+ const startTime = performance.now();
32
+
33
+ // Check le cache persistant sur disque
34
+ const cached = await get(filePath, hash);
35
+ if (cached) {
36
+ if (!isInitial) {
37
+ logSuccess(basename(filePath), Math.round(performance.now() - startTime) + " (cached)");
38
+ }
39
+ return;
40
+ }
41
+
42
+ // Compilation complète par asc
43
+ const result = await compile(filePath, {
44
+ optimizeLevel: isProd ? 3 : 0,
45
+ runtime: isProd ? "minimal" : "stub",
46
+ });
47
+
48
+ if (!result.success || !result.wasmBytes) {
49
+ logError(basename(filePath), result.errors);
50
+ return;
51
+ }
52
+
53
+ // Régénération automatique des définitions TypeScript
54
+ const parsed = parseASExports(text);
55
+ await generateDts(parsed, filePath);
56
+ const dtsContent = await Bun.file(filePath + ".d.ts").text();
57
+
58
+ // Enregistrer dans le cache disque
59
+ await set(filePath, hash, result.wasmBytes, dtsContent);
60
+
61
+ logSuccess(basename(filePath), Math.round(performance.now() - startTime));
62
+ } catch (err) {
63
+ if ((err as NodeJS.ErrnoException).code === "ENOENT") {
64
+ // Fichier potentiellement supprimé
65
+ await invalidate(filePath);
66
+ hashes.delete(filePath);
67
+ } else {
68
+ logError(basename(filePath), [err instanceof Error ? err.message : String(err)]);
69
+ }
70
+ }
71
+ }
72
+
73
+ /**
74
+ * Lance le daemon de surveillance.
75
+ */
76
+ export async function startWatcher() {
77
+ const glob = new Glob("**/*.as");
78
+ const files: string[] = [];
79
+
80
+ // Scan initial récursif avec Glob (ignore node_modules et caches)
81
+ for (const file of glob.scanSync(".")) {
82
+ if (file.includes("node_modules") || file.includes(".cache")) continue;
83
+ const absPath = join(process.cwd(), file);
84
+ files.push(absPath);
85
+ }
86
+
87
+ logStart(files.length);
88
+
89
+ for (const file of files) {
90
+ await processFile(file, true);
91
+
92
+ const watcher = watch(file, async (event) => {
93
+ if (event === "change" || event === "rename") {
94
+ await processFile(file, false);
95
+ }
96
+ });
97
+ watchers.set(file, watcher);
98
+ }
99
+ }
100
+
101
+ /**
102
+ * Compile tout le projet `as` en mode production (optimisation max).
103
+ */
104
+ export async function startBuild() {
105
+ const glob = new Glob("**/*.as");
106
+ const files: string[] = [];
107
+
108
+ for (const file of glob.scanSync(".")) {
109
+ if (file.includes("node_modules") || file.includes(".cache")) continue;
110
+ const absPath = join(process.cwd(), file);
111
+ files.push(absPath);
112
+ }
113
+
114
+ for (const file of files) {
115
+ await processFile(file, true, true);
116
+ }
117
+ }
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Utilitaire de log léger en console sans aucune dépendance,
3
+ * s'appuyant uniquement sur les séquences d'échappement ANSI natives.
4
+ */
5
+
6
+ export function logSuccess(file: string, ms: number | string) {
7
+ // \x1b[32m -> Vert, \x1b[0m -> Reset
8
+ console.log(`\x1b[32m[bun-as] ✓ ${file} compiled in ${ms}ms\x1b[0m`);
9
+ }
10
+
11
+ export function logError(file: string, errors: string[]) {
12
+ // \x1b[31m -> Rouge
13
+ console.error(`\x1b[31m[bun-as] ✗ Error in ${file}\x1b[0m`);
14
+ for (const error of errors) {
15
+ console.error(`\x1b[31m ${error}\x1b[0m`);
16
+ }
17
+ }
18
+
19
+ export function logStart(count: number) {
20
+ // \x1b[36m -> Cyan
21
+ console.log(
22
+ `\x1b[36m[bun-as] Watching ${count} AssemblyScript file${
23
+ count > 1 ? "s" : ""
24
+ }...\x1b[0m`
25
+ );
26
+ }
@@ -0,0 +1,14 @@
1
+ /// <reference path="../../assemblyscript.d.ts" />
2
+
3
+
4
+ export function add(a: i32, b: i32): i32 {
5
+ return (a + b) as i32;
6
+ }
7
+
8
+ export function multiply(a: i32, b: i32): i32 {
9
+ return (a * b) as i32;
10
+ }
11
+
12
+ export function subtract(a: i32, b: i32): i32 {
13
+ return (a - b) as i32;
14
+ }
@@ -0,0 +1,5 @@
1
+ // auto-generated by bun-plugin-assemblyscript — do not edit
2
+
3
+ export function add(a: number, b: number): number;
4
+ export function multiply(a: number, b: number): number;
5
+ export function subtract(a: number, b: number): number;
@@ -0,0 +1,22 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import { add, multiply, subtract } from "./fixtures/math.as";
3
+
4
+ describe("AssemblyScript Math Functions", () => {
5
+ it("should add two numbers correctly", () => {
6
+ expect(add(2, 3)).toBe(5);
7
+ expect(add(-1, 1)).toBe(0);
8
+ expect(add(0, 0)).toBe(0);
9
+ });
10
+
11
+ it("should multiply two numbers correctly", () => {
12
+ expect(multiply(2, 3)).toBe(6);
13
+ expect(multiply(-2, 3)).toBe(-6);
14
+ expect(multiply(0, 5)).toBe(0);
15
+ });
16
+
17
+ it("should subtract two numbers correctly", () => {
18
+ expect(subtract(5, 3)).toBe(2);
19
+ expect(subtract(3, 5)).toBe(-2);
20
+ expect(subtract(0, 0)).toBe(0);
21
+ });
22
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,33 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ESNext",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": [
7
+ "ESNext"
8
+ ],
9
+ "types": [
10
+ "bun-types"
11
+ ],
12
+ "strict": true,
13
+ "esModuleInterop": true,
14
+ "skipLibCheck": true,
15
+ "forceConsistentCasingInFileNames": true,
16
+ "resolveJsonModule": true,
17
+ "declaration": true,
18
+ "declarationMap": true,
19
+ "sourceMap": true,
20
+ "outDir": "./dist"
21
+ },
22
+ "include": [
23
+ "*.ts",
24
+ "src/**/*.ts",
25
+ "test/**/*.ts",
26
+ "assemblyscript.d.ts",
27
+ "**/*.as.d.ts"
28
+ ],
29
+ "exclude": [
30
+ "node_modules",
31
+ "dist"
32
+ ]
33
+ }