Son CV dans un terminal web en Javascript!
https://terminal-cv.gregandev.fr
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
271 lines
10 KiB
271 lines
10 KiB
2 years ago
|
import { fileURLToPath } from './deps.ts';
|
||
|
import { orderedBinary, setNativeFunctions } from './external.js';
|
||
|
orderedBinary.enableNullTermination();
|
||
|
// probably use Deno.build.os
|
||
|
let version = import.meta.url.match(/@([^/]+)\//)?.[1];
|
||
|
//console.log({version});
|
||
|
let libPath = import.meta.url.startsWith('file:') && fileURLToPath(new URL('build/Release/lmdb.node', import.meta.url));
|
||
|
if (!libPath || !exists(libPath)) {
|
||
|
//console.log({ libPath }, 'does not exist')
|
||
|
libPath = (Deno.env.get('LMDB_LIB_PATH') || (tmpdir() + '/lmdb-js-' + (version || '') + '.lib')) as string;
|
||
|
const ARCH = { x86_64: 'x64', aarch64: 'arm64' }
|
||
|
if (!exists(libPath)) {
|
||
|
let os: string = Deno.build.os;
|
||
|
os = os == 'windows' ? 'win32' : os;
|
||
|
os += '-' + ARCH[Deno.build.arch];
|
||
|
let libraryUrl = 'https://cdn.jsdelivr.net/npm/lmdb@' + (version || 'latest') +
|
||
|
'/prebuilds/' + os + '/node.abi93' + (os == 'win32' ? '' : '.glibc') + '.node';
|
||
|
console.log('Download', libraryUrl);
|
||
|
let response = await fetch(libraryUrl);
|
||
|
if (response.status == 200) {
|
||
|
let binaryLibraryBuffer = await response.arrayBuffer();
|
||
|
Deno.writeFileSync(libPath, new Uint8Array(binaryLibraryBuffer));
|
||
|
} else {
|
||
|
throw new Error('Unable to fetch ' + libraryUrl + ', HTTP response: ' + response.status);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
let lmdbLib = Deno.dlopen(libPath, {
|
||
|
// const char* path, char* keyBuffer, Compression* compression, int jsFlags, int flags, int maxDbs,
|
||
|
// int maxReaders, mdb_size_t mapSize, int pageSize, char* encryptionKey
|
||
|
envOpen: { parameters: ['u32', 'u32', 'pointer', 'pointer', 'f64', 'u32', 'u32', 'f64', 'u32', 'pointer'], result: 'i64'},
|
||
|
closeEnv: { parameters: ['f64'], result: 'void'},
|
||
|
freeData: { parameters: ['f64'], result: 'void'},
|
||
|
getAddress: { parameters: ['pointer'], result: 'usize'},
|
||
|
getMaxKeySize: { parameters: ['f64'], result: 'u32'},
|
||
|
openDbi: { parameters: ['f64', 'u32', 'pointer', 'u32', 'f64'], result: 'i64'},
|
||
|
getDbi: { parameters: ['f64'], result: 'u32'},
|
||
|
readerCheck: { parameters: ['f64'], result: 'i32'},
|
||
|
beginTxn: { parameters: ['f64', 'u32'], result: 'i64'},
|
||
|
resetTxn: { parameters: ['f64'], result: 'void'},
|
||
|
renewTxn: { parameters: ['f64'], result: 'i32'},
|
||
|
abortTxn: { parameters: ['f64'], result: 'void'},
|
||
|
commitTxn: { parameters: ['f64'], result: 'i32'},
|
||
|
commitEnvTxn: { parameters: ['f64'], result: 'i32'},
|
||
|
abortEnvTxn: { parameters: ['f64'], result: 'void'},
|
||
|
getError: { parameters: ['i32', 'pointer'], result: 'void'},
|
||
|
dbiGetByBinary: { parameters: ['f64', 'u32'], result: 'u32'},
|
||
|
openCursor: { parameters: ['f64'], result: 'i64'},
|
||
|
cursorRenew: { parameters: ['f64'], result: 'i32'},
|
||
|
cursorClose: { parameters: ['f64'], result: 'i32'},
|
||
|
cursorIterate: { parameters: ['f64'], result: 'i32'},
|
||
|
cursorPosition: { parameters: ['f64', 'u32', 'u32', 'u32', 'f64'], result: 'i32'},
|
||
|
cursorCurrentValue: { parameters: ['f64'], result: 'i32'},
|
||
|
startWriting: { parameters: ['f64', 'f64'], nonblocking: true, result: 'i32'},
|
||
|
compress: { parameters: ['f64', 'f64'], nonblocking: true, result: 'void'},
|
||
|
envWrite: { parameters: ['f64', 'f64'], result: 'i32'},
|
||
|
setGlobalBuffer: { parameters: ['pointer', 'usize'], result: 'void'},
|
||
|
setCompressionBuffer: { parameters: ['f64', 'pointer', 'u32', 'pointer', 'u32'], result: 'void'},
|
||
|
newCompression: { parameters: ['pointer', 'usize', 'u32'], result: 'u64'},
|
||
|
prefetch: { parameters: ['f64', 'f64'], nonblocking: true, result: 'i32'},
|
||
|
envSync: { parameters: ['f64'], nonblocking: true, result: 'i32'},
|
||
|
});
|
||
|
|
||
|
let { envOpen, closeEnv, getAddress, freeData, getMaxKeySize, openDbi, getDbi, readerCheck,
|
||
|
commitEnvTxn, abortEnvTxn, beginTxn, resetTxn, renewTxn, abortTxn, commitTxn, dbiGetByBinary, startWriting, compress, envWrite, openCursor, cursorRenew, cursorClose, cursorIterate, cursorPosition, cursorCurrentValue, setGlobalBuffer: setGlobalBuffer2, setCompressionBuffer, getError, newCompression, prefetch,envSync } = lmdbLib.symbols;
|
||
|
let registry = new FinalizationRegistry<number>(address => {
|
||
|
// when an object is GC'ed, free it in C.
|
||
|
freeData(address);
|
||
|
});
|
||
|
|
||
|
class CBridge {
|
||
|
address: number;
|
||
|
constructor(address: number) {
|
||
|
this.address = address || 0;
|
||
|
if (address) {
|
||
|
registry.register(this, address);
|
||
|
}
|
||
|
}
|
||
|
/* static addMethods(...methods: ) {
|
||
|
for (let method of methods) {
|
||
|
this.prototype[method] = function() {
|
||
|
return symbols[method](this.address, ...arguments);
|
||
|
};
|
||
|
}
|
||
|
}*/
|
||
|
}
|
||
|
const textEncoder = new TextEncoder();
|
||
|
const textDecoder = new TextDecoder();
|
||
|
const MAX_ERROR = 1000;
|
||
|
function checkError(rc: number): number {
|
||
|
if (rc && rc < MAX_ERROR) {
|
||
|
// TODO: Look up error and throw
|
||
|
lmdbError(rc);
|
||
|
}
|
||
|
return rc;
|
||
|
}
|
||
|
function lmdbError(rc: number) {
|
||
|
getError(rc, keyBytes);
|
||
|
let message = textDecoder.decode(keyBytes.subarray(0, keyBytes.indexOf(0))) || ('Error code: ' + rc);
|
||
|
throw new Error(message);
|
||
|
}
|
||
|
let keyBytes: Uint8Array;
|
||
|
class Env extends CBridge {
|
||
|
open(options: any, flags: number, jsFlags: number) {
|
||
|
let rc = envOpen(flags, jsFlags, toCString(options.path), keyBytes = options.keyBytes, 0,
|
||
|
options.maxDbs || 12, options.maxReaders || 126, options.mapSize, options.pageSize, new Uint8Array(0)) as number;
|
||
|
this.address = checkError(rc);
|
||
|
registry.register(this, this.address);
|
||
|
return 0;
|
||
|
}
|
||
|
openDbi(flags: number, name: string, keyType: number, compression: Compression) {
|
||
|
let rc: number = openDbi(this.address, flags, toCString(name), keyType, compression?.address || 0) as number;
|
||
|
if (rc == -30798) { // MDB_NOTFOUND
|
||
|
return;
|
||
|
}
|
||
|
return new Dbi(checkError(rc),
|
||
|
getDbi(rc) as number);
|
||
|
}
|
||
|
close() {
|
||
|
closeEnv(this.address);
|
||
|
}
|
||
|
getMaxKeySize() {
|
||
|
return getMaxKeySize(this.address);
|
||
|
}
|
||
|
readerCheck() {
|
||
|
return readerCheck(this.address);
|
||
|
}
|
||
|
beginTxn(flags: number) {
|
||
|
let rc: number = beginTxn(this.address, flags) as number;
|
||
|
return new Transaction(checkError(rc), flags);
|
||
|
}
|
||
|
commitTxn() {
|
||
|
checkError(commitEnvTxn(this.address) as number);
|
||
|
}
|
||
|
abortTxn() {
|
||
|
abortEnvTxn(this.address);
|
||
|
}
|
||
|
startWriting(instructions: number, callback: (value: number) => number) {
|
||
|
(startWriting(this.address, instructions) as Promise<number>).then(callback);
|
||
|
}
|
||
|
compress(compressionPointer: number, callback: (value: void) => void) {
|
||
|
return (compress(this.address, compressionPointer) as Promise<void>).then(callback);
|
||
|
}
|
||
|
write(instructions: number) {
|
||
|
return checkError(envWrite(this.address, instructions) as number);
|
||
|
}
|
||
|
sync(callback: Function) {
|
||
|
return envSync(this.address).then((result: number) => {
|
||
|
try {
|
||
|
checkError(result);
|
||
|
callback(null);
|
||
|
} catch(error) {
|
||
|
callback(error);
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
//Env.addMethods('startWriting', 'write', 'openDB');
|
||
|
class Dbi extends CBridge {
|
||
|
dbi: number;
|
||
|
constructor(address: number, dbi: number) {
|
||
|
super(address);
|
||
|
this.dbi = dbi;
|
||
|
}
|
||
|
getByBinary(keySize: number): number {
|
||
|
return dbiGetByBinary(this.address, keySize) as number;
|
||
|
}
|
||
|
prefetch(keys: number, callback: () => void): void {
|
||
|
(prefetch(this.address, keys) as Promise<number>).then(() => callback());
|
||
|
}
|
||
|
}
|
||
|
class Transaction extends CBridge {
|
||
|
flags: number;
|
||
|
constructor(address: number, flags: number) {
|
||
|
super(address);
|
||
|
this.flags = flags;
|
||
|
}
|
||
|
reset() {
|
||
|
resetTxn(this.address);
|
||
|
}
|
||
|
renew() {
|
||
|
let rc = renewTxn(this.address) as number;
|
||
|
if (rc)
|
||
|
lmdbError(rc);
|
||
|
}
|
||
|
abort() {
|
||
|
abortTxn(this.address);
|
||
|
}
|
||
|
commit() {
|
||
|
commitTxn(this.address);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
|
||
|
class Compression extends CBridge {
|
||
|
constructor(options: { dictionary: Uint8Array, threshold: number }) {
|
||
|
let dictionary = options.dictionary || new Uint8Array(0);
|
||
|
super(newCompression(dictionary, dictionary.length, options.threshold || 1000) as number);
|
||
|
}
|
||
|
setBuffer(target: Uint8Array, targetLength: number, dictionary: Uint8Array, dictLength: number) {
|
||
|
setCompressionBuffer(this.address, target, targetLength, dictionary, dictLength);
|
||
|
}
|
||
|
}
|
||
|
class Cursor extends CBridge {
|
||
|
constructor(dbi: Dbi) {
|
||
|
super(openCursor(dbi.address) as number);
|
||
|
}
|
||
|
renew() {
|
||
|
cursorRenew(this.address);
|
||
|
}
|
||
|
position(flags: number, offset: number, keySize: number, endKeyAddress: number) {
|
||
|
return cursorPosition(this.address, flags, offset, keySize, endKeyAddress);
|
||
|
}
|
||
|
iterate() {
|
||
|
return cursorIterate(this.address);
|
||
|
}
|
||
|
getCurrentValue() {
|
||
|
return cursorCurrentValue(this.address);
|
||
|
}
|
||
|
close() {
|
||
|
return cursorClose(this.address);
|
||
|
}
|
||
|
}
|
||
|
function toCString(str: string): Uint8Array {
|
||
|
return str == null ? new Uint8Array(0) : textEncoder.encode(str + '\x00');
|
||
|
}
|
||
|
function setGlobalBuffer(buffer: Uint8Array) {
|
||
|
setGlobalBuffer2(buffer, buffer.length);
|
||
|
}
|
||
|
|
||
|
setNativeFunctions({ Env, Compression, Cursor, getAddress, tmpdir, lmdbError, setGlobalBuffer });
|
||
|
export const { toBufferKey: keyValueToBuffer, compareKeys, compareKeys: compareKey, fromBufferKey: bufferToKeyValue } = orderedBinary;
|
||
|
export { ABORT, asBinary, IF_EXISTS } from './write.js';
|
||
|
export { levelup } from './level.js';
|
||
|
export { open, getLastVersion } from './open.js';
|
||
|
|
||
|
// inlined from https://github.com/denoland/deno_std/blob/main/node/os.ts
|
||
|
function tmpdir(): string | null {
|
||
|
/* This follows the node js implementation, but has a few
|
||
|
differences:
|
||
|
* On windows, if none of the environment variables are defined,
|
||
|
we return null.
|
||
|
* On unix we use a plain Deno.env.get, instead of safeGetenv,
|
||
|
which special cases setuid binaries.
|
||
|
* Node removes a single trailing / or \, we remove all.
|
||
|
*/
|
||
|
if (Deno.build.os == 'windows') {
|
||
|
const temp = Deno.env.get("TEMP") || Deno.env.get("TMP");
|
||
|
if (temp) {
|
||
|
return temp.replace(/(?<!:)[/\\]*$/, "");
|
||
|
}
|
||
|
const base = Deno.env.get("SYSTEMROOT") || Deno.env.get("WINDIR");
|
||
|
if (base) {
|
||
|
return base + "\\temp";
|
||
|
}
|
||
|
return null;
|
||
|
} else { // !isWindows
|
||
|
const temp = Deno.env.get("TMPDIR") || Deno.env.get("TMP") ||
|
||
|
Deno.env.get("TEMP") || "/tmp";
|
||
|
return temp.replace(/(?<!^)\/*$/, "");
|
||
|
}
|
||
|
}
|
||
|
function exists(path: string): boolean {
|
||
|
try {
|
||
|
return Boolean(Deno.statSync(path));
|
||
|
} catch (error) {
|
||
|
if (error.name == 'NotFound')
|
||
|
return false
|
||
|
throw error
|
||
|
}
|
||
|
}
|