gesttalt 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/LICENSE +21 -0
- package/README.md +86 -0
- package/binding.gyp +26 -0
- package/browser.mjs +184 -0
- package/index.d.ts +40 -0
- package/index.js +220 -0
- package/index.mjs +230 -0
- package/package.json +61 -0
- package/src/gesttalt.c +287 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Pedro Piñera Buendía
|
|
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,86 @@
|
|
|
1
|
+
# Gesttalt Node.js Bindings
|
|
2
|
+
|
|
3
|
+
High-performance snippets CRUD bindings for Gesttalt, powered by Zig.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install gesttalt
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```js
|
|
14
|
+
const {
|
|
15
|
+
createSnippet,
|
|
16
|
+
readSnippet,
|
|
17
|
+
updateSnippet,
|
|
18
|
+
deleteSnippet,
|
|
19
|
+
} = require('gesttalt');
|
|
20
|
+
|
|
21
|
+
const projectDir = '.';
|
|
22
|
+
const timestamp = 1735148400;
|
|
23
|
+
|
|
24
|
+
const path = createSnippet(
|
|
25
|
+
projectDir,
|
|
26
|
+
timestamp,
|
|
27
|
+
'Example snippet',
|
|
28
|
+
'const x = 1;',
|
|
29
|
+
'example.zig'
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
const snippet = readSnippet(projectDir, timestamp);
|
|
33
|
+
console.log(snippet);
|
|
34
|
+
|
|
35
|
+
updateSnippet(projectDir, timestamp, {
|
|
36
|
+
description: 'Updated description',
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
deleteSnippet(projectDir, timestamp);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Browser
|
|
43
|
+
|
|
44
|
+
This package ships a WASM build for browser runtimes. Because snippets CRUD requires filesystem access, you must provide a WASI-compatible runtime with a mounted directory for the project.
|
|
45
|
+
|
|
46
|
+
```js
|
|
47
|
+
import { init, createSnippet } from 'gesttalt';
|
|
48
|
+
|
|
49
|
+
await init();
|
|
50
|
+
createSnippet('/project', 1735148400, 'Example snippet', 'const x = 1;', 'example.zig');
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Bun and Deno
|
|
54
|
+
|
|
55
|
+
Bun and Deno can use the same binding via their Node.js compatibility layers.
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
bun run test.js
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
deno run --allow-ffi --allow-read --allow-write test.js
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Development
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# From project root
|
|
69
|
+
zig build -Doptimize=ReleaseFast
|
|
70
|
+
|
|
71
|
+
# Build the Node addon
|
|
72
|
+
cd bindings/nodejs
|
|
73
|
+
npm install
|
|
74
|
+
npm test
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## API
|
|
78
|
+
|
|
79
|
+
- `createSnippet(projectDir, timestamp, description, body, filename)`
|
|
80
|
+
- `readSnippet(projectDir, timestamp)`
|
|
81
|
+
- `updateSnippet(projectDir, timestamp, update)`
|
|
82
|
+
- `deleteSnippet(projectDir, timestamp)`
|
|
83
|
+
|
|
84
|
+
## License
|
|
85
|
+
|
|
86
|
+
MIT
|
package/binding.gyp
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"targets": [
|
|
3
|
+
{
|
|
4
|
+
"target_name": "gesttalt",
|
|
5
|
+
"sources": ["src/gesttalt.c"],
|
|
6
|
+
"include_dirs": [],
|
|
7
|
+
"libraries": [
|
|
8
|
+
"<(module_root_dir)/../../zig-out/lib/libgesttalt_ffi.a"
|
|
9
|
+
],
|
|
10
|
+
"cflags": ["-Wall", "-Wextra"],
|
|
11
|
+
"xcode_settings": {
|
|
12
|
+
"OTHER_LDFLAGS": [
|
|
13
|
+
"<(module_root_dir)/../../zig-out/lib/libgesttalt_ffi.a"
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
"conditions": [
|
|
17
|
+
["OS=='linux'", {
|
|
18
|
+
"libraries": ["-lm", "-lpthread"]
|
|
19
|
+
}],
|
|
20
|
+
["OS=='mac'", {
|
|
21
|
+
"libraries": ["-lm"]
|
|
22
|
+
}]
|
|
23
|
+
]
|
|
24
|
+
}
|
|
25
|
+
]
|
|
26
|
+
}
|
package/browser.mjs
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
// Browser-only WASM module for Gesttalt snippets
|
|
2
|
+
|
|
3
|
+
let wasmModule = null;
|
|
4
|
+
let wasmReady = false;
|
|
5
|
+
let initPromise = null;
|
|
6
|
+
|
|
7
|
+
const errorMessages = {
|
|
8
|
+
1: 'Invalid timestamp',
|
|
9
|
+
2: 'Duplicate snippet',
|
|
10
|
+
3: 'Snippet not found',
|
|
11
|
+
4: 'Invalid extension',
|
|
12
|
+
5: 'Invalid description',
|
|
13
|
+
6: 'Invalid filename',
|
|
14
|
+
7: 'Parse error',
|
|
15
|
+
8: 'I/O error',
|
|
16
|
+
9: 'Out of memory',
|
|
17
|
+
100: 'Unknown error',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export class GesttaltError extends Error {
|
|
21
|
+
constructor(code, message) {
|
|
22
|
+
super(message);
|
|
23
|
+
this.name = 'GesttaltError';
|
|
24
|
+
this.code = code;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function init() {
|
|
29
|
+
if (wasmReady) return;
|
|
30
|
+
if (initPromise) return initPromise;
|
|
31
|
+
|
|
32
|
+
initPromise = (async () => {
|
|
33
|
+
const response = await fetch(new URL('./gesttalt.wasm', import.meta.url));
|
|
34
|
+
const wasmBuffer = await response.arrayBuffer();
|
|
35
|
+
const result = await WebAssembly.instantiate(wasmBuffer, {});
|
|
36
|
+
wasmModule = result.instance;
|
|
37
|
+
wasmReady = true;
|
|
38
|
+
})();
|
|
39
|
+
|
|
40
|
+
return initPromise;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function ensureWasmReady() {
|
|
44
|
+
if (!wasmReady) {
|
|
45
|
+
throw new Error('Gesttalt WASM not initialized. Call init() first.');
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function throwForCode(code) {
|
|
50
|
+
if (code === 0) return;
|
|
51
|
+
const message = errorMessages[code] || errorMessages[100];
|
|
52
|
+
throw new GesttaltError(code, message);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function writeString(exports, memory, value) {
|
|
56
|
+
const encoder = new TextEncoder();
|
|
57
|
+
const bytes = encoder.encode(value);
|
|
58
|
+
const ptr = exports.alloc(bytes.length);
|
|
59
|
+
if (!ptr) {
|
|
60
|
+
throw new GesttaltError(9, errorMessages[9]);
|
|
61
|
+
}
|
|
62
|
+
new Uint8Array(memory.buffer, ptr, bytes.length).set(bytes);
|
|
63
|
+
return { ptr, len: bytes.length };
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function readResult(exports, memory) {
|
|
67
|
+
const resultPtr = exports.get_result_ptr();
|
|
68
|
+
const resultLen = exports.get_result_len();
|
|
69
|
+
if (!resultPtr || resultLen === 0) {
|
|
70
|
+
return '';
|
|
71
|
+
}
|
|
72
|
+
const decoder = new TextDecoder();
|
|
73
|
+
const resultStr = decoder.decode(new Uint8Array(memory.buffer, resultPtr, resultLen));
|
|
74
|
+
exports.free_result();
|
|
75
|
+
return resultStr;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function createSnippetWasm(projectDir, timestamp, description, body, filename) {
|
|
79
|
+
ensureWasmReady();
|
|
80
|
+
const exports = wasmModule.exports;
|
|
81
|
+
const memory = exports.memory;
|
|
82
|
+
|
|
83
|
+
const project = writeString(exports, memory, projectDir);
|
|
84
|
+
const desc = writeString(exports, memory, description);
|
|
85
|
+
const bodyBuf = writeString(exports, memory, body);
|
|
86
|
+
const file = writeString(exports, memory, filename);
|
|
87
|
+
|
|
88
|
+
const rc = exports.snippet_create(
|
|
89
|
+
project.ptr, project.len,
|
|
90
|
+
BigInt(timestamp),
|
|
91
|
+
desc.ptr, desc.len,
|
|
92
|
+
bodyBuf.ptr, bodyBuf.len,
|
|
93
|
+
file.ptr, file.len
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
exports.dealloc(project.ptr, project.len);
|
|
97
|
+
exports.dealloc(desc.ptr, desc.len);
|
|
98
|
+
exports.dealloc(bodyBuf.ptr, bodyBuf.len);
|
|
99
|
+
exports.dealloc(file.ptr, file.len);
|
|
100
|
+
|
|
101
|
+
throwForCode(rc);
|
|
102
|
+
return readResult(exports, memory);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function readSnippetWasm(projectDir, timestamp) {
|
|
106
|
+
ensureWasmReady();
|
|
107
|
+
const exports = wasmModule.exports;
|
|
108
|
+
const memory = exports.memory;
|
|
109
|
+
|
|
110
|
+
const project = writeString(exports, memory, projectDir);
|
|
111
|
+
const rc = exports.snippet_read(project.ptr, project.len, BigInt(timestamp));
|
|
112
|
+
exports.dealloc(project.ptr, project.len);
|
|
113
|
+
|
|
114
|
+
throwForCode(rc);
|
|
115
|
+
return readResult(exports, memory);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function updateSnippetWasm(projectDir, timestamp, description, body, filename) {
|
|
119
|
+
ensureWasmReady();
|
|
120
|
+
const exports = wasmModule.exports;
|
|
121
|
+
const memory = exports.memory;
|
|
122
|
+
|
|
123
|
+
const project = writeString(exports, memory, projectDir);
|
|
124
|
+
const desc = description != null ? writeString(exports, memory, description) : null;
|
|
125
|
+
const bodyBuf = body != null ? writeString(exports, memory, body) : null;
|
|
126
|
+
const file = filename != null ? writeString(exports, memory, filename) : null;
|
|
127
|
+
|
|
128
|
+
const rc = exports.snippet_update(
|
|
129
|
+
project.ptr, project.len,
|
|
130
|
+
BigInt(timestamp),
|
|
131
|
+
desc ? desc.ptr : 0, desc ? desc.len : 0,
|
|
132
|
+
bodyBuf ? bodyBuf.ptr : 0, bodyBuf ? bodyBuf.len : 0,
|
|
133
|
+
file ? file.ptr : 0, file ? file.len : 0
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
exports.dealloc(project.ptr, project.len);
|
|
137
|
+
if (desc) exports.dealloc(desc.ptr, desc.len);
|
|
138
|
+
if (bodyBuf) exports.dealloc(bodyBuf.ptr, bodyBuf.len);
|
|
139
|
+
if (file) exports.dealloc(file.ptr, file.len);
|
|
140
|
+
|
|
141
|
+
throwForCode(rc);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function deleteSnippetWasm(projectDir, timestamp) {
|
|
145
|
+
ensureWasmReady();
|
|
146
|
+
const exports = wasmModule.exports;
|
|
147
|
+
const memory = exports.memory;
|
|
148
|
+
|
|
149
|
+
const project = writeString(exports, memory, projectDir);
|
|
150
|
+
const rc = exports.snippet_delete(project.ptr, project.len, BigInt(timestamp));
|
|
151
|
+
exports.dealloc(project.ptr, project.len);
|
|
152
|
+
|
|
153
|
+
throwForCode(rc);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
export function createSnippet(projectDir, timestamp, description, body, filename) {
|
|
157
|
+
return createSnippetWasm(projectDir, timestamp, description, body, filename);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
export function readSnippet(projectDir, timestamp) {
|
|
161
|
+
const json = readSnippetWasm(projectDir, timestamp);
|
|
162
|
+
return json ? JSON.parse(json) : null;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
export function updateSnippet(projectDir, timestamp, update = {}) {
|
|
166
|
+
const description = update.description ?? null;
|
|
167
|
+
const body = update.body ?? null;
|
|
168
|
+
const filename = update.filename ?? null;
|
|
169
|
+
return updateSnippetWasm(projectDir, timestamp, description, body, filename);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
export function deleteSnippet(projectDir, timestamp) {
|
|
173
|
+
return deleteSnippetWasm(projectDir, timestamp);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
export function isNative() {
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
export function isWasm() {
|
|
181
|
+
return wasmReady;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
export default { init, createSnippet, readSnippet, updateSnippet, deleteSnippet, isNative, isWasm, GesttaltError };
|
package/index.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
export interface Snippet {
|
|
2
|
+
id: string;
|
|
3
|
+
date: string;
|
|
4
|
+
description: string;
|
|
5
|
+
filename: string;
|
|
6
|
+
code: string;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface SnippetUpdate {
|
|
10
|
+
description?: string;
|
|
11
|
+
body?: string;
|
|
12
|
+
filename?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export class GesttaltError extends Error {
|
|
16
|
+
code: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function init(): Promise<void>;
|
|
20
|
+
|
|
21
|
+
export function createSnippet(
|
|
22
|
+
projectDir: string,
|
|
23
|
+
timestamp: number,
|
|
24
|
+
description: string,
|
|
25
|
+
body: string,
|
|
26
|
+
filename: string
|
|
27
|
+
): string;
|
|
28
|
+
|
|
29
|
+
export function readSnippet(projectDir: string, timestamp: number): Snippet | null;
|
|
30
|
+
|
|
31
|
+
export function updateSnippet(
|
|
32
|
+
projectDir: string,
|
|
33
|
+
timestamp: number,
|
|
34
|
+
update?: SnippetUpdate
|
|
35
|
+
): void;
|
|
36
|
+
|
|
37
|
+
export function deleteSnippet(projectDir: string, timestamp: number): void;
|
|
38
|
+
|
|
39
|
+
export function isNative(): boolean;
|
|
40
|
+
export function isWasm(): boolean;
|
package/index.js
ADDED
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
|
|
4
|
+
let native = null;
|
|
5
|
+
let wasmModule = null;
|
|
6
|
+
let wasmReady = false;
|
|
7
|
+
|
|
8
|
+
const errorMessages = {
|
|
9
|
+
1: 'Invalid timestamp',
|
|
10
|
+
2: 'Duplicate snippet',
|
|
11
|
+
3: 'Snippet not found',
|
|
12
|
+
4: 'Invalid extension',
|
|
13
|
+
5: 'Invalid description',
|
|
14
|
+
6: 'Invalid filename',
|
|
15
|
+
7: 'Parse error',
|
|
16
|
+
8: 'I/O error',
|
|
17
|
+
9: 'Out of memory',
|
|
18
|
+
100: 'Unknown error',
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
class GesttaltError extends Error {
|
|
22
|
+
constructor(code, message) {
|
|
23
|
+
super(message);
|
|
24
|
+
this.name = 'GesttaltError';
|
|
25
|
+
this.code = code;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
native = require('node-gyp-build')(__dirname);
|
|
31
|
+
} catch (e) {
|
|
32
|
+
try {
|
|
33
|
+
native = require('./build/Release/gesttalt.node');
|
|
34
|
+
} catch (e2) {
|
|
35
|
+
// Native not available, will use WASM
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
async function init() {
|
|
40
|
+
if (native) {
|
|
41
|
+
wasmReady = true;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (wasmModule) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const wasmPath = path.join(__dirname, 'gesttalt.wasm');
|
|
50
|
+
const wasmBuffer = fs.readFileSync(wasmPath);
|
|
51
|
+
const result = await WebAssembly.instantiate(wasmBuffer, {});
|
|
52
|
+
wasmModule = result.instance;
|
|
53
|
+
wasmReady = true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function ensureWasmReady() {
|
|
57
|
+
if (!wasmReady) {
|
|
58
|
+
throw new Error('Gesttalt WASM not initialized. Call init() first.');
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function throwForCode(code) {
|
|
63
|
+
if (code === 0) return;
|
|
64
|
+
const message = errorMessages[code] || errorMessages[100];
|
|
65
|
+
throw new GesttaltError(code, message);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function writeString(exports, memory, value) {
|
|
69
|
+
const encoder = new TextEncoder();
|
|
70
|
+
const bytes = encoder.encode(value);
|
|
71
|
+
const ptr = exports.alloc(bytes.length);
|
|
72
|
+
if (!ptr) {
|
|
73
|
+
throw new GesttaltError(9, errorMessages[9]);
|
|
74
|
+
}
|
|
75
|
+
new Uint8Array(memory.buffer, ptr, bytes.length).set(bytes);
|
|
76
|
+
return { ptr, len: bytes.length };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function readResult(exports, memory) {
|
|
80
|
+
const resultPtr = exports.get_result_ptr();
|
|
81
|
+
const resultLen = exports.get_result_len();
|
|
82
|
+
if (!resultPtr || resultLen === 0) {
|
|
83
|
+
return '';
|
|
84
|
+
}
|
|
85
|
+
const decoder = new TextDecoder();
|
|
86
|
+
const resultStr = decoder.decode(new Uint8Array(memory.buffer, resultPtr, resultLen));
|
|
87
|
+
exports.free_result();
|
|
88
|
+
return resultStr;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function createSnippetWasm(projectDir, timestamp, description, body, filename) {
|
|
92
|
+
ensureWasmReady();
|
|
93
|
+
const exports = wasmModule.exports;
|
|
94
|
+
const memory = exports.memory;
|
|
95
|
+
|
|
96
|
+
const project = writeString(exports, memory, projectDir);
|
|
97
|
+
const desc = writeString(exports, memory, description);
|
|
98
|
+
const bodyBuf = writeString(exports, memory, body);
|
|
99
|
+
const file = writeString(exports, memory, filename);
|
|
100
|
+
|
|
101
|
+
const rc = exports.snippet_create(
|
|
102
|
+
project.ptr, project.len,
|
|
103
|
+
BigInt(timestamp),
|
|
104
|
+
desc.ptr, desc.len,
|
|
105
|
+
bodyBuf.ptr, bodyBuf.len,
|
|
106
|
+
file.ptr, file.len
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
exports.dealloc(project.ptr, project.len);
|
|
110
|
+
exports.dealloc(desc.ptr, desc.len);
|
|
111
|
+
exports.dealloc(bodyBuf.ptr, bodyBuf.len);
|
|
112
|
+
exports.dealloc(file.ptr, file.len);
|
|
113
|
+
|
|
114
|
+
throwForCode(rc);
|
|
115
|
+
return readResult(exports, memory);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function readSnippetWasm(projectDir, timestamp) {
|
|
119
|
+
ensureWasmReady();
|
|
120
|
+
const exports = wasmModule.exports;
|
|
121
|
+
const memory = exports.memory;
|
|
122
|
+
|
|
123
|
+
const project = writeString(exports, memory, projectDir);
|
|
124
|
+
const rc = exports.snippet_read(project.ptr, project.len, BigInt(timestamp));
|
|
125
|
+
exports.dealloc(project.ptr, project.len);
|
|
126
|
+
|
|
127
|
+
throwForCode(rc);
|
|
128
|
+
return readResult(exports, memory);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function updateSnippetWasm(projectDir, timestamp, description, body, filename) {
|
|
132
|
+
ensureWasmReady();
|
|
133
|
+
const exports = wasmModule.exports;
|
|
134
|
+
const memory = exports.memory;
|
|
135
|
+
|
|
136
|
+
const project = writeString(exports, memory, projectDir);
|
|
137
|
+
const desc = description != null ? writeString(exports, memory, description) : null;
|
|
138
|
+
const bodyBuf = body != null ? writeString(exports, memory, body) : null;
|
|
139
|
+
const file = filename != null ? writeString(exports, memory, filename) : null;
|
|
140
|
+
|
|
141
|
+
const rc = exports.snippet_update(
|
|
142
|
+
project.ptr, project.len,
|
|
143
|
+
BigInt(timestamp),
|
|
144
|
+
desc ? desc.ptr : 0, desc ? desc.len : 0,
|
|
145
|
+
bodyBuf ? bodyBuf.ptr : 0, bodyBuf ? bodyBuf.len : 0,
|
|
146
|
+
file ? file.ptr : 0, file ? file.len : 0
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
exports.dealloc(project.ptr, project.len);
|
|
150
|
+
if (desc) exports.dealloc(desc.ptr, desc.len);
|
|
151
|
+
if (bodyBuf) exports.dealloc(bodyBuf.ptr, bodyBuf.len);
|
|
152
|
+
if (file) exports.dealloc(file.ptr, file.len);
|
|
153
|
+
|
|
154
|
+
throwForCode(rc);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function deleteSnippetWasm(projectDir, timestamp) {
|
|
158
|
+
ensureWasmReady();
|
|
159
|
+
const exports = wasmModule.exports;
|
|
160
|
+
const memory = exports.memory;
|
|
161
|
+
|
|
162
|
+
const project = writeString(exports, memory, projectDir);
|
|
163
|
+
const rc = exports.snippet_delete(project.ptr, project.len, BigInt(timestamp));
|
|
164
|
+
exports.dealloc(project.ptr, project.len);
|
|
165
|
+
|
|
166
|
+
throwForCode(rc);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function createSnippet(projectDir, timestamp, description, body, filename) {
|
|
170
|
+
if (native) {
|
|
171
|
+
return native.createSnippet(projectDir, timestamp, description, body, filename);
|
|
172
|
+
}
|
|
173
|
+
return createSnippetWasm(projectDir, timestamp, description, body, filename);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function readSnippet(projectDir, timestamp) {
|
|
177
|
+
if (native) {
|
|
178
|
+
const json = native.readSnippet(projectDir, timestamp);
|
|
179
|
+
return json ? JSON.parse(json) : null;
|
|
180
|
+
}
|
|
181
|
+
const json = readSnippetWasm(projectDir, timestamp);
|
|
182
|
+
return json ? JSON.parse(json) : null;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function updateSnippet(projectDir, timestamp, update = {}) {
|
|
186
|
+
const description = update.description ?? null;
|
|
187
|
+
const body = update.body ?? null;
|
|
188
|
+
const filename = update.filename ?? null;
|
|
189
|
+
|
|
190
|
+
if (native) {
|
|
191
|
+
return native.updateSnippet(projectDir, timestamp, description, body, filename);
|
|
192
|
+
}
|
|
193
|
+
return updateSnippetWasm(projectDir, timestamp, description, body, filename);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function deleteSnippet(projectDir, timestamp) {
|
|
197
|
+
if (native) {
|
|
198
|
+
return native.deleteSnippet(projectDir, timestamp);
|
|
199
|
+
}
|
|
200
|
+
return deleteSnippetWasm(projectDir, timestamp);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function isNative() {
|
|
204
|
+
return native !== null;
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function isWasm() {
|
|
208
|
+
return native === null && wasmReady;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
module.exports = {
|
|
212
|
+
init,
|
|
213
|
+
createSnippet,
|
|
214
|
+
readSnippet,
|
|
215
|
+
updateSnippet,
|
|
216
|
+
deleteSnippet,
|
|
217
|
+
isNative,
|
|
218
|
+
isWasm,
|
|
219
|
+
GesttaltError,
|
|
220
|
+
};
|
package/index.mjs
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { createRequire } from 'module';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { readFileSync } from 'fs';
|
|
5
|
+
|
|
6
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
const require = createRequire(import.meta.url);
|
|
8
|
+
|
|
9
|
+
let native = null;
|
|
10
|
+
let wasmModule = null;
|
|
11
|
+
let wasmReady = false;
|
|
12
|
+
let initPromise = null;
|
|
13
|
+
|
|
14
|
+
const errorMessages = {
|
|
15
|
+
1: 'Invalid timestamp',
|
|
16
|
+
2: 'Duplicate snippet',
|
|
17
|
+
3: 'Snippet not found',
|
|
18
|
+
4: 'Invalid extension',
|
|
19
|
+
5: 'Invalid description',
|
|
20
|
+
6: 'Invalid filename',
|
|
21
|
+
7: 'Parse error',
|
|
22
|
+
8: 'I/O error',
|
|
23
|
+
9: 'Out of memory',
|
|
24
|
+
100: 'Unknown error',
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export class GesttaltError extends Error {
|
|
28
|
+
constructor(code, message) {
|
|
29
|
+
super(message);
|
|
30
|
+
this.name = 'GesttaltError';
|
|
31
|
+
this.code = code;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
try {
|
|
36
|
+
native = require('node-gyp-build')(__dirname);
|
|
37
|
+
} catch (e) {
|
|
38
|
+
try {
|
|
39
|
+
native = require('./build/Release/gesttalt.node');
|
|
40
|
+
} catch (e2) {
|
|
41
|
+
// Native not available, will use WASM
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function init() {
|
|
46
|
+
if (native) {
|
|
47
|
+
wasmReady = true;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (initPromise) {
|
|
52
|
+
return initPromise;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
initPromise = (async () => {
|
|
56
|
+
const wasmPath = join(__dirname, 'gesttalt.wasm');
|
|
57
|
+
const wasmBuffer = readFileSync(wasmPath);
|
|
58
|
+
const result = await WebAssembly.instantiate(wasmBuffer, {});
|
|
59
|
+
wasmModule = result.instance;
|
|
60
|
+
wasmReady = true;
|
|
61
|
+
})();
|
|
62
|
+
|
|
63
|
+
return initPromise;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function ensureWasmReady() {
|
|
67
|
+
if (!wasmReady) {
|
|
68
|
+
throw new Error('Gesttalt WASM not initialized. Call init() first.');
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function throwForCode(code) {
|
|
73
|
+
if (code === 0) return;
|
|
74
|
+
const message = errorMessages[code] || errorMessages[100];
|
|
75
|
+
throw new GesttaltError(code, message);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function writeString(exports, memory, value) {
|
|
79
|
+
const encoder = new TextEncoder();
|
|
80
|
+
const bytes = encoder.encode(value);
|
|
81
|
+
const ptr = exports.alloc(bytes.length);
|
|
82
|
+
if (!ptr) {
|
|
83
|
+
throw new GesttaltError(9, errorMessages[9]);
|
|
84
|
+
}
|
|
85
|
+
new Uint8Array(memory.buffer, ptr, bytes.length).set(bytes);
|
|
86
|
+
return { ptr, len: bytes.length };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function readResult(exports, memory) {
|
|
90
|
+
const resultPtr = exports.get_result_ptr();
|
|
91
|
+
const resultLen = exports.get_result_len();
|
|
92
|
+
if (!resultPtr || resultLen === 0) {
|
|
93
|
+
return '';
|
|
94
|
+
}
|
|
95
|
+
const decoder = new TextDecoder();
|
|
96
|
+
const resultStr = decoder.decode(new Uint8Array(memory.buffer, resultPtr, resultLen));
|
|
97
|
+
exports.free_result();
|
|
98
|
+
return resultStr;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function createSnippetWasm(projectDir, timestamp, description, body, filename) {
|
|
102
|
+
ensureWasmReady();
|
|
103
|
+
const exports = wasmModule.exports;
|
|
104
|
+
const memory = exports.memory;
|
|
105
|
+
|
|
106
|
+
const project = writeString(exports, memory, projectDir);
|
|
107
|
+
const desc = writeString(exports, memory, description);
|
|
108
|
+
const bodyBuf = writeString(exports, memory, body);
|
|
109
|
+
const file = writeString(exports, memory, filename);
|
|
110
|
+
|
|
111
|
+
const rc = exports.snippet_create(
|
|
112
|
+
project.ptr, project.len,
|
|
113
|
+
BigInt(timestamp),
|
|
114
|
+
desc.ptr, desc.len,
|
|
115
|
+
bodyBuf.ptr, bodyBuf.len,
|
|
116
|
+
file.ptr, file.len
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
exports.dealloc(project.ptr, project.len);
|
|
120
|
+
exports.dealloc(desc.ptr, desc.len);
|
|
121
|
+
exports.dealloc(bodyBuf.ptr, bodyBuf.len);
|
|
122
|
+
exports.dealloc(file.ptr, file.len);
|
|
123
|
+
|
|
124
|
+
throwForCode(rc);
|
|
125
|
+
return readResult(exports, memory);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function readSnippetWasm(projectDir, timestamp) {
|
|
129
|
+
ensureWasmReady();
|
|
130
|
+
const exports = wasmModule.exports;
|
|
131
|
+
const memory = exports.memory;
|
|
132
|
+
|
|
133
|
+
const project = writeString(exports, memory, projectDir);
|
|
134
|
+
const rc = exports.snippet_read(project.ptr, project.len, BigInt(timestamp));
|
|
135
|
+
exports.dealloc(project.ptr, project.len);
|
|
136
|
+
|
|
137
|
+
throwForCode(rc);
|
|
138
|
+
return readResult(exports, memory);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function updateSnippetWasm(projectDir, timestamp, description, body, filename) {
|
|
142
|
+
ensureWasmReady();
|
|
143
|
+
const exports = wasmModule.exports;
|
|
144
|
+
const memory = exports.memory;
|
|
145
|
+
|
|
146
|
+
const project = writeString(exports, memory, projectDir);
|
|
147
|
+
const desc = description != null ? writeString(exports, memory, description) : null;
|
|
148
|
+
const bodyBuf = body != null ? writeString(exports, memory, body) : null;
|
|
149
|
+
const file = filename != null ? writeString(exports, memory, filename) : null;
|
|
150
|
+
|
|
151
|
+
const rc = exports.snippet_update(
|
|
152
|
+
project.ptr, project.len,
|
|
153
|
+
BigInt(timestamp),
|
|
154
|
+
desc ? desc.ptr : 0, desc ? desc.len : 0,
|
|
155
|
+
bodyBuf ? bodyBuf.ptr : 0, bodyBuf ? bodyBuf.len : 0,
|
|
156
|
+
file ? file.ptr : 0, file ? file.len : 0
|
|
157
|
+
);
|
|
158
|
+
|
|
159
|
+
exports.dealloc(project.ptr, project.len);
|
|
160
|
+
if (desc) exports.dealloc(desc.ptr, desc.len);
|
|
161
|
+
if (bodyBuf) exports.dealloc(bodyBuf.ptr, bodyBuf.len);
|
|
162
|
+
if (file) exports.dealloc(file.ptr, file.len);
|
|
163
|
+
|
|
164
|
+
throwForCode(rc);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function deleteSnippetWasm(projectDir, timestamp) {
|
|
168
|
+
ensureWasmReady();
|
|
169
|
+
const exports = wasmModule.exports;
|
|
170
|
+
const memory = exports.memory;
|
|
171
|
+
|
|
172
|
+
const project = writeString(exports, memory, projectDir);
|
|
173
|
+
const rc = exports.snippet_delete(project.ptr, project.len, BigInt(timestamp));
|
|
174
|
+
exports.dealloc(project.ptr, project.len);
|
|
175
|
+
|
|
176
|
+
throwForCode(rc);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export function createSnippet(projectDir, timestamp, description, body, filename) {
|
|
180
|
+
if (native) {
|
|
181
|
+
return native.createSnippet(projectDir, timestamp, description, body, filename);
|
|
182
|
+
}
|
|
183
|
+
return createSnippetWasm(projectDir, timestamp, description, body, filename);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export function readSnippet(projectDir, timestamp) {
|
|
187
|
+
if (native) {
|
|
188
|
+
const json = native.readSnippet(projectDir, timestamp);
|
|
189
|
+
return json ? JSON.parse(json) : null;
|
|
190
|
+
}
|
|
191
|
+
const json = readSnippetWasm(projectDir, timestamp);
|
|
192
|
+
return json ? JSON.parse(json) : null;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
export function updateSnippet(projectDir, timestamp, update = {}) {
|
|
196
|
+
const description = update.description ?? null;
|
|
197
|
+
const body = update.body ?? null;
|
|
198
|
+
const filename = update.filename ?? null;
|
|
199
|
+
|
|
200
|
+
if (native) {
|
|
201
|
+
return native.updateSnippet(projectDir, timestamp, description, body, filename);
|
|
202
|
+
}
|
|
203
|
+
return updateSnippetWasm(projectDir, timestamp, description, body, filename);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
export function deleteSnippet(projectDir, timestamp) {
|
|
207
|
+
if (native) {
|
|
208
|
+
return native.deleteSnippet(projectDir, timestamp);
|
|
209
|
+
}
|
|
210
|
+
return deleteSnippetWasm(projectDir, timestamp);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
export function isNative() {
|
|
214
|
+
return native !== null;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
export function isWasm() {
|
|
218
|
+
return native === null && wasmReady;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
export default {
|
|
222
|
+
init,
|
|
223
|
+
createSnippet,
|
|
224
|
+
readSnippet,
|
|
225
|
+
updateSnippet,
|
|
226
|
+
deleteSnippet,
|
|
227
|
+
isNative,
|
|
228
|
+
isWasm,
|
|
229
|
+
GesttaltError,
|
|
230
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gesttalt",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Gesttalt snippets CRUD bindings powered by Zig",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"module": "index.mjs",
|
|
7
|
+
"types": "index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"browser": "./browser.mjs",
|
|
11
|
+
"import": "./index.mjs",
|
|
12
|
+
"require": "./index.js",
|
|
13
|
+
"types": "./index.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "node-gyp rebuild",
|
|
18
|
+
"install": "node-gyp-build || node-gyp rebuild",
|
|
19
|
+
"test": "node test.js",
|
|
20
|
+
"prebuild": "prebuildify --napi --strip"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"gesttalt",
|
|
24
|
+
"snippets",
|
|
25
|
+
"crud",
|
|
26
|
+
"zig",
|
|
27
|
+
"ffi",
|
|
28
|
+
"wasm",
|
|
29
|
+
"browser"
|
|
30
|
+
],
|
|
31
|
+
"author": "Pedro Pinera Buendia",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "https://github.com/pepicrft/gesttalt.git"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=16.0.0"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"node-gyp-build": "^4.8.4"
|
|
42
|
+
},
|
|
43
|
+
"devDependencies": {
|
|
44
|
+
"node-gyp": "^10.0.0",
|
|
45
|
+
"prebuildify": "^6.0.0"
|
|
46
|
+
},
|
|
47
|
+
"files": [
|
|
48
|
+
"index.js",
|
|
49
|
+
"index.mjs",
|
|
50
|
+
"index.d.ts",
|
|
51
|
+
"browser.mjs",
|
|
52
|
+
"gesttalt.wasm",
|
|
53
|
+
"binding.gyp",
|
|
54
|
+
"src/",
|
|
55
|
+
"prebuilds/"
|
|
56
|
+
],
|
|
57
|
+
"browser": {
|
|
58
|
+
"./index.js": "./browser.mjs",
|
|
59
|
+
"./index.mjs": "./browser.mjs"
|
|
60
|
+
}
|
|
61
|
+
}
|
package/src/gesttalt.c
ADDED
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
#include <node_api.h>
|
|
2
|
+
#include <string.h>
|
|
3
|
+
#include <stdlib.h>
|
|
4
|
+
#include <stdint.h>
|
|
5
|
+
|
|
6
|
+
int gesttalt_snippet_create(const char *project_dir, int64_t timestamp,
|
|
7
|
+
const char *description, const char *body,
|
|
8
|
+
const char *filename, char **out_path);
|
|
9
|
+
int gesttalt_snippet_read(const char *project_dir, int64_t timestamp,
|
|
10
|
+
char **out_json);
|
|
11
|
+
int gesttalt_snippet_update(const char *project_dir, int64_t timestamp,
|
|
12
|
+
const char *description, const char *body,
|
|
13
|
+
const char *filename);
|
|
14
|
+
int gesttalt_snippet_delete(const char *project_dir, int64_t timestamp);
|
|
15
|
+
void gesttalt_free(char *ptr);
|
|
16
|
+
|
|
17
|
+
static const char *error_message(int code) {
|
|
18
|
+
switch (code) {
|
|
19
|
+
case 1: return "Invalid timestamp";
|
|
20
|
+
case 2: return "Duplicate snippet";
|
|
21
|
+
case 3: return "Snippet not found";
|
|
22
|
+
case 4: return "Invalid extension";
|
|
23
|
+
case 5: return "Invalid description";
|
|
24
|
+
case 6: return "Invalid filename";
|
|
25
|
+
case 7: return "Parse error";
|
|
26
|
+
case 8: return "I/O error";
|
|
27
|
+
case 9: return "Out of memory";
|
|
28
|
+
default: return "Unknown error";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
static void throw_error(napi_env env, int code) {
|
|
33
|
+
napi_value msg;
|
|
34
|
+
napi_value err;
|
|
35
|
+
napi_value code_val;
|
|
36
|
+
|
|
37
|
+
napi_create_string_utf8(env, error_message(code), NAPI_AUTO_LENGTH, &msg);
|
|
38
|
+
napi_create_error(env, NULL, msg, &err);
|
|
39
|
+
napi_create_int32(env, code, &code_val);
|
|
40
|
+
napi_set_named_property(env, err, "code", code_val);
|
|
41
|
+
napi_throw(env, err);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static bool get_string(napi_env env, napi_value value, char **out, size_t *out_len) {
|
|
45
|
+
napi_status status = napi_get_value_string_utf8(env, value, NULL, 0, out_len);
|
|
46
|
+
if (status != napi_ok) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
*out = malloc(*out_len + 1);
|
|
50
|
+
if (!*out) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
napi_get_value_string_utf8(env, value, *out, *out_len + 1, out_len);
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
static bool get_optional_string(napi_env env, napi_value value, char **out) {
|
|
58
|
+
napi_valuetype type;
|
|
59
|
+
napi_typeof(env, value, &type);
|
|
60
|
+
if (type == napi_null || type == napi_undefined) {
|
|
61
|
+
*out = NULL;
|
|
62
|
+
return true;
|
|
63
|
+
}
|
|
64
|
+
size_t len = 0;
|
|
65
|
+
if (!get_string(env, value, out, &len)) {
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
return true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
static napi_value CreateSnippet(napi_env env, napi_callback_info info) {
|
|
72
|
+
size_t argc = 5;
|
|
73
|
+
napi_value args[5];
|
|
74
|
+
napi_status status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
|
|
75
|
+
if (status != napi_ok || argc < 5) {
|
|
76
|
+
napi_throw_error(env, NULL, "Expected 5 arguments");
|
|
77
|
+
return NULL;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
char *project_dir = NULL;
|
|
81
|
+
char *description = NULL;
|
|
82
|
+
char *body = NULL;
|
|
83
|
+
char *filename = NULL;
|
|
84
|
+
size_t tmp_len = 0;
|
|
85
|
+
int64_t timestamp = 0;
|
|
86
|
+
|
|
87
|
+
if (!get_string(env, args[0], &project_dir, &tmp_len)) {
|
|
88
|
+
napi_throw_error(env, NULL, "project_dir must be a string");
|
|
89
|
+
return NULL;
|
|
90
|
+
}
|
|
91
|
+
if (napi_get_value_int64(env, args[1], ×tamp) != napi_ok) {
|
|
92
|
+
free(project_dir);
|
|
93
|
+
napi_throw_error(env, NULL, "timestamp must be a number");
|
|
94
|
+
return NULL;
|
|
95
|
+
}
|
|
96
|
+
if (!get_string(env, args[2], &description, &tmp_len)) {
|
|
97
|
+
free(project_dir);
|
|
98
|
+
napi_throw_error(env, NULL, "description must be a string");
|
|
99
|
+
return NULL;
|
|
100
|
+
}
|
|
101
|
+
if (!get_string(env, args[3], &body, &tmp_len)) {
|
|
102
|
+
free(project_dir);
|
|
103
|
+
free(description);
|
|
104
|
+
napi_throw_error(env, NULL, "body must be a string");
|
|
105
|
+
return NULL;
|
|
106
|
+
}
|
|
107
|
+
if (!get_string(env, args[4], &filename, &tmp_len)) {
|
|
108
|
+
free(project_dir);
|
|
109
|
+
free(description);
|
|
110
|
+
free(body);
|
|
111
|
+
napi_throw_error(env, NULL, "filename must be a string");
|
|
112
|
+
return NULL;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
char *out_path = NULL;
|
|
116
|
+
int rc = gesttalt_snippet_create(project_dir, timestamp, description, body, filename, &out_path);
|
|
117
|
+
|
|
118
|
+
free(project_dir);
|
|
119
|
+
free(description);
|
|
120
|
+
free(body);
|
|
121
|
+
free(filename);
|
|
122
|
+
|
|
123
|
+
if (rc != 0) {
|
|
124
|
+
throw_error(env, rc);
|
|
125
|
+
return NULL;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
napi_value result;
|
|
129
|
+
if (out_path) {
|
|
130
|
+
napi_create_string_utf8(env, out_path, strlen(out_path), &result);
|
|
131
|
+
gesttalt_free(out_path);
|
|
132
|
+
} else {
|
|
133
|
+
napi_get_undefined(env, &result);
|
|
134
|
+
}
|
|
135
|
+
return result;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
static napi_value ReadSnippet(napi_env env, napi_callback_info info) {
|
|
139
|
+
size_t argc = 2;
|
|
140
|
+
napi_value args[2];
|
|
141
|
+
napi_status status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
|
|
142
|
+
if (status != napi_ok || argc < 2) {
|
|
143
|
+
napi_throw_error(env, NULL, "Expected 2 arguments");
|
|
144
|
+
return NULL;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
char *project_dir = NULL;
|
|
148
|
+
size_t tmp_len = 0;
|
|
149
|
+
int64_t timestamp = 0;
|
|
150
|
+
|
|
151
|
+
if (!get_string(env, args[0], &project_dir, &tmp_len)) {
|
|
152
|
+
napi_throw_error(env, NULL, "project_dir must be a string");
|
|
153
|
+
return NULL;
|
|
154
|
+
}
|
|
155
|
+
if (napi_get_value_int64(env, args[1], ×tamp) != napi_ok) {
|
|
156
|
+
free(project_dir);
|
|
157
|
+
napi_throw_error(env, NULL, "timestamp must be a number");
|
|
158
|
+
return NULL;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
char *out_json = NULL;
|
|
162
|
+
int rc = gesttalt_snippet_read(project_dir, timestamp, &out_json);
|
|
163
|
+
free(project_dir);
|
|
164
|
+
|
|
165
|
+
if (rc != 0) {
|
|
166
|
+
throw_error(env, rc);
|
|
167
|
+
return NULL;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
napi_value result;
|
|
171
|
+
if (out_json) {
|
|
172
|
+
napi_create_string_utf8(env, out_json, strlen(out_json), &result);
|
|
173
|
+
gesttalt_free(out_json);
|
|
174
|
+
} else {
|
|
175
|
+
napi_get_undefined(env, &result);
|
|
176
|
+
}
|
|
177
|
+
return result;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
static napi_value UpdateSnippet(napi_env env, napi_callback_info info) {
|
|
181
|
+
size_t argc = 5;
|
|
182
|
+
napi_value args[5];
|
|
183
|
+
napi_status status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
|
|
184
|
+
if (status != napi_ok || argc < 5) {
|
|
185
|
+
napi_throw_error(env, NULL, "Expected 5 arguments");
|
|
186
|
+
return NULL;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
char *project_dir = NULL;
|
|
190
|
+
char *description = NULL;
|
|
191
|
+
char *body = NULL;
|
|
192
|
+
char *filename = NULL;
|
|
193
|
+
size_t tmp_len = 0;
|
|
194
|
+
int64_t timestamp = 0;
|
|
195
|
+
|
|
196
|
+
if (!get_string(env, args[0], &project_dir, &tmp_len)) {
|
|
197
|
+
napi_throw_error(env, NULL, "project_dir must be a string");
|
|
198
|
+
return NULL;
|
|
199
|
+
}
|
|
200
|
+
if (napi_get_value_int64(env, args[1], ×tamp) != napi_ok) {
|
|
201
|
+
free(project_dir);
|
|
202
|
+
napi_throw_error(env, NULL, "timestamp must be a number");
|
|
203
|
+
return NULL;
|
|
204
|
+
}
|
|
205
|
+
if (!get_optional_string(env, args[2], &description) ||
|
|
206
|
+
!get_optional_string(env, args[3], &body) ||
|
|
207
|
+
!get_optional_string(env, args[4], &filename)) {
|
|
208
|
+
free(project_dir);
|
|
209
|
+
free(description);
|
|
210
|
+
free(body);
|
|
211
|
+
free(filename);
|
|
212
|
+
napi_throw_error(env, NULL, "Invalid update fields");
|
|
213
|
+
return NULL;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
int rc = gesttalt_snippet_update(project_dir, timestamp, description, body, filename);
|
|
217
|
+
|
|
218
|
+
free(project_dir);
|
|
219
|
+
free(description);
|
|
220
|
+
free(body);
|
|
221
|
+
free(filename);
|
|
222
|
+
|
|
223
|
+
if (rc != 0) {
|
|
224
|
+
throw_error(env, rc);
|
|
225
|
+
return NULL;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
napi_value result;
|
|
229
|
+
napi_get_undefined(env, &result);
|
|
230
|
+
return result;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
static napi_value DeleteSnippet(napi_env env, napi_callback_info info) {
|
|
234
|
+
size_t argc = 2;
|
|
235
|
+
napi_value args[2];
|
|
236
|
+
napi_status status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
|
|
237
|
+
if (status != napi_ok || argc < 2) {
|
|
238
|
+
napi_throw_error(env, NULL, "Expected 2 arguments");
|
|
239
|
+
return NULL;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
char *project_dir = NULL;
|
|
243
|
+
size_t tmp_len = 0;
|
|
244
|
+
int64_t timestamp = 0;
|
|
245
|
+
|
|
246
|
+
if (!get_string(env, args[0], &project_dir, &tmp_len)) {
|
|
247
|
+
napi_throw_error(env, NULL, "project_dir must be a string");
|
|
248
|
+
return NULL;
|
|
249
|
+
}
|
|
250
|
+
if (napi_get_value_int64(env, args[1], ×tamp) != napi_ok) {
|
|
251
|
+
free(project_dir);
|
|
252
|
+
napi_throw_error(env, NULL, "timestamp must be a number");
|
|
253
|
+
return NULL;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
int rc = gesttalt_snippet_delete(project_dir, timestamp);
|
|
257
|
+
free(project_dir);
|
|
258
|
+
|
|
259
|
+
if (rc != 0) {
|
|
260
|
+
throw_error(env, rc);
|
|
261
|
+
return NULL;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
napi_value result;
|
|
265
|
+
napi_get_undefined(env, &result);
|
|
266
|
+
return result;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
static napi_value Init(napi_env env, napi_value exports) {
|
|
270
|
+
napi_value fn;
|
|
271
|
+
|
|
272
|
+
napi_create_function(env, NULL, 0, CreateSnippet, NULL, &fn);
|
|
273
|
+
napi_set_named_property(env, exports, "createSnippet", fn);
|
|
274
|
+
|
|
275
|
+
napi_create_function(env, NULL, 0, ReadSnippet, NULL, &fn);
|
|
276
|
+
napi_set_named_property(env, exports, "readSnippet", fn);
|
|
277
|
+
|
|
278
|
+
napi_create_function(env, NULL, 0, UpdateSnippet, NULL, &fn);
|
|
279
|
+
napi_set_named_property(env, exports, "updateSnippet", fn);
|
|
280
|
+
|
|
281
|
+
napi_create_function(env, NULL, 0, DeleteSnippet, NULL, &fn);
|
|
282
|
+
napi_set_named_property(env, exports, "deleteSnippet", fn);
|
|
283
|
+
|
|
284
|
+
return exports;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
NAPI_MODULE(NODE_GYP_MODULE_NAME, Init)
|