@levalicious/server-memory 0.0.12 → 0.0.14
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/binding.gyp +16 -0
- package/dist/scripts/textrank-experiment.js +618 -0
- package/dist/server.js +77 -3
- package/dist/src/kb_load.js +396 -0
- package/dist/src/memoryfile.js +15 -2
- package/dist/src/stringtable.js +24 -6
- package/dist/tests/memory-server.test.js +129 -0
- package/dist/tests/test-utils.js +6 -0
- package/native/binding.c +340 -0
- package/native/memoryfile.c +343 -0
- package/native/memoryfile.h +82 -0
- package/package.json +10 -3
|
@@ -1027,4 +1027,133 @@ describe('MCP Memory Server E2E Tests', () => {
|
|
|
1027
1027
|
});
|
|
1028
1028
|
});
|
|
1029
1029
|
});
|
|
1030
|
+
describe('kb_load', () => {
|
|
1031
|
+
let docFile;
|
|
1032
|
+
beforeEach(async () => {
|
|
1033
|
+
docFile = path.join(testDir, 'test-doc.txt');
|
|
1034
|
+
});
|
|
1035
|
+
it('should reject non-plaintext extensions', async () => {
|
|
1036
|
+
const pdfPath = path.join(testDir, 'test.pdf');
|
|
1037
|
+
await fs.writeFile(pdfPath, 'fake pdf content');
|
|
1038
|
+
await expect(callTool(client, 'kb_load', { filePath: pdfPath })).rejects.toThrow(/Unsupported file extension/);
|
|
1039
|
+
});
|
|
1040
|
+
it('should reject files with no extension', async () => {
|
|
1041
|
+
const noExtPath = path.join(testDir, 'noext');
|
|
1042
|
+
await fs.writeFile(noExtPath, 'some content');
|
|
1043
|
+
await expect(callTool(client, 'kb_load', { filePath: noExtPath })).rejects.toThrow(/no extension/);
|
|
1044
|
+
});
|
|
1045
|
+
it('should reject missing files', async () => {
|
|
1046
|
+
await expect(callTool(client, 'kb_load', { filePath: path.join(testDir, 'nonexistent.txt') })).rejects.toThrow(/Failed to read file/);
|
|
1047
|
+
});
|
|
1048
|
+
it('should load a small document and create entities + relations', async () => {
|
|
1049
|
+
const text = [
|
|
1050
|
+
'Abstract interpretation is a theory of sound approximation of program semantics.',
|
|
1051
|
+
'The key idea is to compute over abstract domains instead of concrete domains.',
|
|
1052
|
+
'This enables static analysis to scale to large programs.',
|
|
1053
|
+
'Galois connections formalize the relationship between abstract and concrete.',
|
|
1054
|
+
'Widening operators ensure termination of the analysis.',
|
|
1055
|
+
].join(' ');
|
|
1056
|
+
await fs.writeFile(docFile, text);
|
|
1057
|
+
const result = await callTool(client, 'kb_load', { filePath: docFile });
|
|
1058
|
+
expect(result.document).toBe('test-doc');
|
|
1059
|
+
expect(result.entitiesCreated).toBeGreaterThan(0);
|
|
1060
|
+
expect(result.relationsCreated).toBeGreaterThan(0);
|
|
1061
|
+
expect(result.stats.chunks).toBeGreaterThan(0);
|
|
1062
|
+
expect(result.stats.sentences).toBeGreaterThan(0);
|
|
1063
|
+
});
|
|
1064
|
+
it('should create Document, TextChunk, and DocumentIndex entities', async () => {
|
|
1065
|
+
await fs.writeFile(docFile, 'The quick brown fox jumps over the lazy dog. The fox was very quick and the dog was very lazy. This sentence makes the document long enough to have multiple chunks for testing purposes and analysis.');
|
|
1066
|
+
await callTool(client, 'kb_load', { filePath: docFile });
|
|
1067
|
+
// Check document entity
|
|
1068
|
+
const docResult = await callTool(client, 'open_nodes', { names: ['test-doc'] });
|
|
1069
|
+
expect(docResult.entities.items).toHaveLength(1);
|
|
1070
|
+
expect(docResult.entities.items[0].entityType).toBe('Document');
|
|
1071
|
+
// Check index entity
|
|
1072
|
+
const indexResult = await callTool(client, 'open_nodes', { names: ['test-doc__index'] });
|
|
1073
|
+
expect(indexResult.entities.items).toHaveLength(1);
|
|
1074
|
+
expect(indexResult.entities.items[0].entityType).toBe('DocumentIndex');
|
|
1075
|
+
// Check TextChunk entities exist via type query
|
|
1076
|
+
const chunks = await callTool(client, 'get_entities_by_type', { entityType: 'TextChunk' });
|
|
1077
|
+
expect(chunks.items.length).toBeGreaterThan(0);
|
|
1078
|
+
for (const chunk of chunks.items) {
|
|
1079
|
+
expect(chunk.observations.length).toBeLessThanOrEqual(2);
|
|
1080
|
+
for (const obs of chunk.observations) {
|
|
1081
|
+
expect(obs.length).toBeLessThanOrEqual(140);
|
|
1082
|
+
}
|
|
1083
|
+
}
|
|
1084
|
+
});
|
|
1085
|
+
it('should create chain relations (starts_with, ends_with, follows)', async () => {
|
|
1086
|
+
const text = 'First sentence of the document goes here. ' +
|
|
1087
|
+
'Second sentence adds more content to the document. ' +
|
|
1088
|
+
'Third sentence continues the thought further along. ' +
|
|
1089
|
+
'Fourth sentence wraps up the document nicely.';
|
|
1090
|
+
await fs.writeFile(docFile, text);
|
|
1091
|
+
await callTool(client, 'kb_load', { filePath: docFile });
|
|
1092
|
+
// Get the document's relations
|
|
1093
|
+
const docResult = await callTool(client, 'open_nodes', { names: ['test-doc'] });
|
|
1094
|
+
const relTypes = docResult.relations.items.map(r => r.relationType);
|
|
1095
|
+
expect(relTypes).toContain('starts_with');
|
|
1096
|
+
expect(relTypes).toContain('has_index');
|
|
1097
|
+
});
|
|
1098
|
+
it('should create index → chunk highlight relations', async () => {
|
|
1099
|
+
const sentences = [];
|
|
1100
|
+
for (let i = 0; i < 20; i++) {
|
|
1101
|
+
sentences.push(`This is sentence number ${i} about abstract interpretation and program analysis with different keywords each time.`);
|
|
1102
|
+
}
|
|
1103
|
+
await fs.writeFile(docFile, sentences.join(' '));
|
|
1104
|
+
const result = await callTool(client, 'kb_load', { filePath: docFile });
|
|
1105
|
+
expect(result.stats.indexHighlights).toBeGreaterThan(0);
|
|
1106
|
+
// Open the index and verify it has highlight relations
|
|
1107
|
+
const indexResult = await callTool(client, 'open_nodes', { names: ['test-doc__index'] });
|
|
1108
|
+
const highlightRels = indexResult.relations.items.filter(r => r.relationType === 'highlights');
|
|
1109
|
+
expect(highlightRels.length).toBeGreaterThan(0);
|
|
1110
|
+
});
|
|
1111
|
+
it('should respect custom title', async () => {
|
|
1112
|
+
await fs.writeFile(docFile, 'Some document content that is long enough to process.');
|
|
1113
|
+
const result = await callTool(client, 'kb_load', {
|
|
1114
|
+
filePath: docFile,
|
|
1115
|
+
title: 'my-custom-title',
|
|
1116
|
+
});
|
|
1117
|
+
expect(result.document).toBe('my-custom-title');
|
|
1118
|
+
const docResult = await callTool(client, 'open_nodes', { names: ['my-custom-title'] });
|
|
1119
|
+
expect(docResult.entities.items).toHaveLength(1);
|
|
1120
|
+
});
|
|
1121
|
+
it('should not create duplicate document entity on reload with different content', async () => {
|
|
1122
|
+
await fs.writeFile(docFile, 'Short doc for dedup testing purposes here.');
|
|
1123
|
+
await callTool(client, 'kb_load', { filePath: docFile });
|
|
1124
|
+
// Second load with different content but same title — Document entity
|
|
1125
|
+
// already exists with entityType 'Document' and no observations,
|
|
1126
|
+
// so it gets silently skipped. But the index entity already exists
|
|
1127
|
+
// with different observations, so it should error.
|
|
1128
|
+
await fs.writeFile(docFile, 'Completely different content for dedup testing now.');
|
|
1129
|
+
await expect(callTool(client, 'kb_load', { filePath: docFile })).rejects.toThrow(/already exists/);
|
|
1130
|
+
});
|
|
1131
|
+
it('should enforce observation length limits', async () => {
|
|
1132
|
+
// Create a document with very long words that might challenge splitting
|
|
1133
|
+
const longWord = 'a'.repeat(200);
|
|
1134
|
+
await fs.writeFile(docFile, `${longWord} is a very long word that tests our splitting logic handles edge cases.`);
|
|
1135
|
+
const result = await callTool(client, 'kb_load', { filePath: docFile });
|
|
1136
|
+
expect(result.entitiesCreated).toBeGreaterThan(0);
|
|
1137
|
+
// All observations should be within limits
|
|
1138
|
+
const chunks = await callTool(client, 'get_entities_by_type', { entityType: 'TextChunk' });
|
|
1139
|
+
for (const chunk of chunks.items) {
|
|
1140
|
+
for (const obs of chunk.observations) {
|
|
1141
|
+
expect(obs.length).toBeLessThanOrEqual(140);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
});
|
|
1145
|
+
it('should accept various plaintext extensions', async () => {
|
|
1146
|
+
const extensions = ['.txt', '.md', '.tex', '.py', '.ts', '.c'];
|
|
1147
|
+
for (const ext of extensions) {
|
|
1148
|
+
const filePath = path.join(testDir, `test${ext}`);
|
|
1149
|
+
await fs.writeFile(filePath, 'Some plaintext content for extension testing purposes here.');
|
|
1150
|
+
// Should not throw on validation — use a unique title per extension
|
|
1151
|
+
const result = await callTool(client, 'kb_load', {
|
|
1152
|
+
filePath,
|
|
1153
|
+
title: `ext-test-${ext.slice(1)}`,
|
|
1154
|
+
});
|
|
1155
|
+
expect(result.entitiesCreated).toBeGreaterThan(0);
|
|
1156
|
+
}
|
|
1157
|
+
});
|
|
1158
|
+
});
|
|
1030
1159
|
});
|
package/dist/tests/test-utils.js
CHANGED
|
@@ -31,6 +31,12 @@ const PAGINATED_TOOLS = new Set([
|
|
|
31
31
|
'get_orphaned_entities',
|
|
32
32
|
]);
|
|
33
33
|
export async function callTool(client, name, args) {
|
|
34
|
+
// Verify tool is discoverable via ListTools before calling it
|
|
35
|
+
const listing = await client.listTools();
|
|
36
|
+
const toolNames = listing.tools.map((t) => t.name);
|
|
37
|
+
if (!toolNames.includes(name)) {
|
|
38
|
+
throw new Error(`Tool "${name}" is not listed in ListTools. Available: ${toolNames.join(', ')}`);
|
|
39
|
+
}
|
|
34
40
|
const result = await client.callTool({ name, arguments: args });
|
|
35
41
|
const content = result.content;
|
|
36
42
|
if (!content || content.length === 0) {
|
package/native/binding.c
ADDED
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* N-API binding for memoryfile
|
|
3
|
+
*
|
|
4
|
+
* Exposes the C memoryfile allocator to Node.js.
|
|
5
|
+
* Each MemoryFile handle is wrapped in a pointerless external.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
#define NAPI_VERSION 8
|
|
9
|
+
#include <node_api.h>
|
|
10
|
+
#include <stdlib.h>
|
|
11
|
+
#include <string.h>
|
|
12
|
+
#include "memoryfile.h"
|
|
13
|
+
|
|
14
|
+
/* =========================================================================
|
|
15
|
+
* Helpers
|
|
16
|
+
* ========================================================================= */
|
|
17
|
+
|
|
18
|
+
#define NAPI_CALL(call) \
|
|
19
|
+
do { \
|
|
20
|
+
napi_status status = (call); \
|
|
21
|
+
if (status != napi_ok) { \
|
|
22
|
+
napi_throw_error(env, NULL, "N-API call failed: " #call); \
|
|
23
|
+
return NULL; \
|
|
24
|
+
} \
|
|
25
|
+
} while (0)
|
|
26
|
+
|
|
27
|
+
static napi_value make_u64(napi_env env, u64 val) {
|
|
28
|
+
/* Use BigInt for u64 to avoid precision loss */
|
|
29
|
+
napi_value result;
|
|
30
|
+
NAPI_CALL(napi_create_bigint_uint64(env, val, &result));
|
|
31
|
+
return result;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static u64 get_u64(napi_env env, napi_value val) {
|
|
35
|
+
bool lossless;
|
|
36
|
+
uint64_t result;
|
|
37
|
+
napi_get_value_bigint_uint64(env, val, &result, &lossless);
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
static void mf_release(napi_env env, void *data, void *hint) {
|
|
42
|
+
(void)env; (void)hint;
|
|
43
|
+
memfile_t *mf = (memfile_t*)data;
|
|
44
|
+
if (mf) {
|
|
45
|
+
memfile_close(mf); /* no-op if already closed */
|
|
46
|
+
mf->mmap_base = NULL;
|
|
47
|
+
mf->header = NULL;
|
|
48
|
+
free(mf);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static memfile_t *unwrap_mf(napi_env env, napi_value val) {
|
|
53
|
+
memfile_t *mf;
|
|
54
|
+
napi_get_value_external(env, val, (void**)&mf);
|
|
55
|
+
return mf;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/* =========================================================================
|
|
59
|
+
* memfile_open(path: string, initialSize: number) => external
|
|
60
|
+
* ========================================================================= */
|
|
61
|
+
|
|
62
|
+
static napi_value n_memfile_open(napi_env env, napi_callback_info info) {
|
|
63
|
+
size_t argc = 2;
|
|
64
|
+
napi_value argv[2];
|
|
65
|
+
NAPI_CALL(napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
|
|
66
|
+
|
|
67
|
+
/* Get path string */
|
|
68
|
+
char path[4096];
|
|
69
|
+
size_t path_len;
|
|
70
|
+
NAPI_CALL(napi_get_value_string_utf8(env, argv[0], path, sizeof(path), &path_len));
|
|
71
|
+
|
|
72
|
+
/* Get initial size */
|
|
73
|
+
uint32_t initial_size;
|
|
74
|
+
NAPI_CALL(napi_get_value_uint32(env, argv[1], &initial_size));
|
|
75
|
+
|
|
76
|
+
memfile_t *mf = memfile_open(path, (size_t)initial_size);
|
|
77
|
+
if (!mf) {
|
|
78
|
+
napi_throw_error(env, NULL, "memfile_open failed");
|
|
79
|
+
return NULL;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
napi_value result;
|
|
83
|
+
NAPI_CALL(napi_create_external(env, mf, mf_release, NULL, &result));
|
|
84
|
+
return result;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/* =========================================================================
|
|
88
|
+
* memfile_close(handle: external) => void
|
|
89
|
+
* ========================================================================= */
|
|
90
|
+
|
|
91
|
+
static napi_value n_memfile_close(napi_env env, napi_callback_info info) {
|
|
92
|
+
size_t argc = 1;
|
|
93
|
+
napi_value argv[1];
|
|
94
|
+
NAPI_CALL(napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
|
|
95
|
+
|
|
96
|
+
memfile_t *mf = unwrap_mf(env, argv[0]);
|
|
97
|
+
memfile_close(mf);
|
|
98
|
+
|
|
99
|
+
return NULL;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/* =========================================================================
|
|
103
|
+
* memfile_sync(handle: external) => void
|
|
104
|
+
* ========================================================================= */
|
|
105
|
+
|
|
106
|
+
static napi_value n_memfile_sync(napi_env env, napi_callback_info info) {
|
|
107
|
+
size_t argc = 1;
|
|
108
|
+
napi_value argv[1];
|
|
109
|
+
NAPI_CALL(napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
|
|
110
|
+
|
|
111
|
+
memfile_t *mf = unwrap_mf(env, argv[0]);
|
|
112
|
+
memfile_sync(mf);
|
|
113
|
+
|
|
114
|
+
return NULL;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/* =========================================================================
|
|
118
|
+
* memfile_alloc(handle: external, size: bigint) => bigint (offset)
|
|
119
|
+
* ========================================================================= */
|
|
120
|
+
|
|
121
|
+
static napi_value n_memfile_alloc(napi_env env, napi_callback_info info) {
|
|
122
|
+
size_t argc = 2;
|
|
123
|
+
napi_value argv[2];
|
|
124
|
+
NAPI_CALL(napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
|
|
125
|
+
|
|
126
|
+
memfile_t *mf = unwrap_mf(env, argv[0]);
|
|
127
|
+
u64 size = get_u64(env, argv[1]);
|
|
128
|
+
|
|
129
|
+
u64 offset = memfile_alloc(mf, size);
|
|
130
|
+
return make_u64(env, offset);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/* =========================================================================
|
|
134
|
+
* memfile_free(handle: external, offset: bigint) => void
|
|
135
|
+
* ========================================================================= */
|
|
136
|
+
|
|
137
|
+
static napi_value n_memfile_free(napi_env env, napi_callback_info info) {
|
|
138
|
+
size_t argc = 2;
|
|
139
|
+
napi_value argv[2];
|
|
140
|
+
NAPI_CALL(napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
|
|
141
|
+
|
|
142
|
+
memfile_t *mf = unwrap_mf(env, argv[0]);
|
|
143
|
+
u64 offset = get_u64(env, argv[1]);
|
|
144
|
+
|
|
145
|
+
memfile_free(mf, offset);
|
|
146
|
+
|
|
147
|
+
return NULL;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/* =========================================================================
|
|
151
|
+
* memfile_coalesce(handle: external) => void
|
|
152
|
+
* ========================================================================= */
|
|
153
|
+
|
|
154
|
+
static napi_value n_memfile_coalesce(napi_env env, napi_callback_info info) {
|
|
155
|
+
size_t argc = 1;
|
|
156
|
+
napi_value argv[1];
|
|
157
|
+
NAPI_CALL(napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
|
|
158
|
+
|
|
159
|
+
memfile_t *mf = unwrap_mf(env, argv[0]);
|
|
160
|
+
memfile_coalesce(mf);
|
|
161
|
+
|
|
162
|
+
return NULL;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* =========================================================================
|
|
166
|
+
* memfile_read(handle: external, offset: bigint, length: bigint) => Buffer
|
|
167
|
+
* ========================================================================= */
|
|
168
|
+
|
|
169
|
+
static napi_value n_memfile_read(napi_env env, napi_callback_info info) {
|
|
170
|
+
size_t argc = 3;
|
|
171
|
+
napi_value argv[3];
|
|
172
|
+
NAPI_CALL(napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
|
|
173
|
+
|
|
174
|
+
memfile_t *mf = unwrap_mf(env, argv[0]);
|
|
175
|
+
u64 offset = get_u64(env, argv[1]);
|
|
176
|
+
u64 len = get_u64(env, argv[2]);
|
|
177
|
+
|
|
178
|
+
/* Create a Node Buffer and copy data into it */
|
|
179
|
+
void *buf_data;
|
|
180
|
+
napi_value result;
|
|
181
|
+
NAPI_CALL(napi_create_buffer(env, (size_t)len, &buf_data, &result));
|
|
182
|
+
|
|
183
|
+
if (memfile_read(mf, offset, buf_data, len) < 0) {
|
|
184
|
+
napi_throw_error(env, NULL, "memfile_read: offset/length out of bounds");
|
|
185
|
+
return NULL;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/* =========================================================================
|
|
192
|
+
* memfile_write(handle: external, offset: bigint, data: Buffer) => void
|
|
193
|
+
* ========================================================================= */
|
|
194
|
+
|
|
195
|
+
static napi_value n_memfile_write(napi_env env, napi_callback_info info) {
|
|
196
|
+
size_t argc = 3;
|
|
197
|
+
napi_value argv[3];
|
|
198
|
+
NAPI_CALL(napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
|
|
199
|
+
|
|
200
|
+
memfile_t *mf = unwrap_mf(env, argv[0]);
|
|
201
|
+
u64 offset = get_u64(env, argv[1]);
|
|
202
|
+
|
|
203
|
+
/* Get Buffer data */
|
|
204
|
+
void *buf_data;
|
|
205
|
+
size_t buf_len;
|
|
206
|
+
NAPI_CALL(napi_get_buffer_info(env, argv[2], &buf_data, &buf_len));
|
|
207
|
+
|
|
208
|
+
if (memfile_write(mf, offset, buf_data, buf_len) < 0) {
|
|
209
|
+
napi_throw_error(env, NULL, "memfile_write: offset/length out of bounds");
|
|
210
|
+
return NULL;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return NULL;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/* =========================================================================
|
|
217
|
+
* memfile_lock_shared(handle: external) => void
|
|
218
|
+
* ========================================================================= */
|
|
219
|
+
|
|
220
|
+
static napi_value n_memfile_lock_shared(napi_env env, napi_callback_info info) {
|
|
221
|
+
size_t argc = 1;
|
|
222
|
+
napi_value argv[1];
|
|
223
|
+
NAPI_CALL(napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
|
|
224
|
+
|
|
225
|
+
memfile_t *mf = unwrap_mf(env, argv[0]);
|
|
226
|
+
if (memfile_lock_shared(mf) < 0) {
|
|
227
|
+
napi_throw_error(env, NULL, "memfile_lock_shared failed");
|
|
228
|
+
return NULL;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
return NULL;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/* =========================================================================
|
|
235
|
+
* memfile_lock_exclusive(handle: external) => void
|
|
236
|
+
* ========================================================================= */
|
|
237
|
+
|
|
238
|
+
static napi_value n_memfile_lock_exclusive(napi_env env, napi_callback_info info) {
|
|
239
|
+
size_t argc = 1;
|
|
240
|
+
napi_value argv[1];
|
|
241
|
+
NAPI_CALL(napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
|
|
242
|
+
|
|
243
|
+
memfile_t *mf = unwrap_mf(env, argv[0]);
|
|
244
|
+
if (memfile_lock_exclusive(mf) < 0) {
|
|
245
|
+
napi_throw_error(env, NULL, "memfile_lock_exclusive failed");
|
|
246
|
+
return NULL;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
return NULL;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/* =========================================================================
|
|
253
|
+
* memfile_unlock(handle: external) => void
|
|
254
|
+
* ========================================================================= */
|
|
255
|
+
|
|
256
|
+
static napi_value n_memfile_unlock(napi_env env, napi_callback_info info) {
|
|
257
|
+
size_t argc = 1;
|
|
258
|
+
napi_value argv[1];
|
|
259
|
+
NAPI_CALL(napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
|
|
260
|
+
|
|
261
|
+
memfile_t *mf = unwrap_mf(env, argv[0]);
|
|
262
|
+
if (memfile_unlock(mf) < 0) {
|
|
263
|
+
napi_throw_error(env, NULL, "memfile_unlock failed");
|
|
264
|
+
return NULL;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return NULL;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/* =========================================================================
|
|
271
|
+
* memfile_stats(handle: external) => { fileSize, allocated, freeListHead }
|
|
272
|
+
* ========================================================================= */
|
|
273
|
+
|
|
274
|
+
static napi_value n_memfile_stats(napi_env env, napi_callback_info info) {
|
|
275
|
+
size_t argc = 1;
|
|
276
|
+
napi_value argv[1];
|
|
277
|
+
NAPI_CALL(napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
|
|
278
|
+
|
|
279
|
+
memfile_t *mf = unwrap_mf(env, argv[0]);
|
|
280
|
+
|
|
281
|
+
napi_value result;
|
|
282
|
+
NAPI_CALL(napi_create_object(env, &result));
|
|
283
|
+
|
|
284
|
+
napi_value v;
|
|
285
|
+
NAPI_CALL(napi_create_bigint_uint64(env, mf->header->file_size, &v));
|
|
286
|
+
NAPI_CALL(napi_set_named_property(env, result, "fileSize", v));
|
|
287
|
+
|
|
288
|
+
NAPI_CALL(napi_create_bigint_uint64(env, mf->header->allocated, &v));
|
|
289
|
+
NAPI_CALL(napi_set_named_property(env, result, "allocated", v));
|
|
290
|
+
|
|
291
|
+
NAPI_CALL(napi_create_bigint_uint64(env, mf->header->free_list_head, &v));
|
|
292
|
+
NAPI_CALL(napi_set_named_property(env, result, "freeListHead", v));
|
|
293
|
+
|
|
294
|
+
return result;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/* =========================================================================
|
|
298
|
+
* memfile_refresh(handle: external) => void
|
|
299
|
+
* ========================================================================= */
|
|
300
|
+
|
|
301
|
+
static napi_value n_memfile_refresh(napi_env env, napi_callback_info info) {
|
|
302
|
+
size_t argc = 1;
|
|
303
|
+
napi_value argv[1];
|
|
304
|
+
NAPI_CALL(napi_get_cb_info(env, info, &argc, argv, NULL, NULL));
|
|
305
|
+
|
|
306
|
+
memfile_t *mf = unwrap_mf(env, argv[0]);
|
|
307
|
+
if (memfile_refresh(mf) < 0) {
|
|
308
|
+
napi_throw_error(env, NULL, "memfile_refresh failed");
|
|
309
|
+
return NULL;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
return NULL;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/* =========================================================================
|
|
316
|
+
* Module init
|
|
317
|
+
* ========================================================================= */
|
|
318
|
+
|
|
319
|
+
#define EXPORT_FN(name, fn) do { \
|
|
320
|
+
napi_value _fn; \
|
|
321
|
+
napi_create_function(env, name, NAPI_AUTO_LENGTH, fn, NULL, &_fn); \
|
|
322
|
+
napi_set_named_property(env, exports, name, _fn); \
|
|
323
|
+
} while(0)
|
|
324
|
+
|
|
325
|
+
NAPI_MODULE_INIT(/* napi_env env, napi_value exports */) {
|
|
326
|
+
EXPORT_FN("open", n_memfile_open);
|
|
327
|
+
EXPORT_FN("close", n_memfile_close);
|
|
328
|
+
EXPORT_FN("sync", n_memfile_sync);
|
|
329
|
+
EXPORT_FN("alloc", n_memfile_alloc);
|
|
330
|
+
EXPORT_FN("free", n_memfile_free);
|
|
331
|
+
EXPORT_FN("coalesce", n_memfile_coalesce);
|
|
332
|
+
EXPORT_FN("read", n_memfile_read);
|
|
333
|
+
EXPORT_FN("write", n_memfile_write);
|
|
334
|
+
EXPORT_FN("lockShared", n_memfile_lock_shared);
|
|
335
|
+
EXPORT_FN("lockExclusive", n_memfile_lock_exclusive);
|
|
336
|
+
EXPORT_FN("unlock", n_memfile_unlock);
|
|
337
|
+
EXPORT_FN("stats", n_memfile_stats);
|
|
338
|
+
EXPORT_FN("refresh", n_memfile_refresh);
|
|
339
|
+
return exports;
|
|
340
|
+
}
|