@neottia/repository-store 0.2.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 +76 -0
- package/binding.gyp +9 -0
- package/dist/errors.d.ts +43 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +69 -0
- package/dist/errors.js.map +1 -0
- package/dist/files.d.ts +19 -0
- package/dist/files.d.ts.map +1 -0
- package/dist/files.js +113 -0
- package/dist/files.js.map +1 -0
- package/dist/index.d.ts +14 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/fault-injection.d.ts +13 -0
- package/dist/internal/fault-injection.d.ts.map +1 -0
- package/dist/internal/fault-injection.js +68 -0
- package/dist/internal/fault-injection.js.map +1 -0
- package/dist/internal/filesystem.d.ts +25 -0
- package/dist/internal/filesystem.d.ts.map +1 -0
- package/dist/internal/filesystem.js +199 -0
- package/dist/internal/filesystem.js.map +1 -0
- package/dist/internal/model.d.ts +43 -0
- package/dist/internal/model.d.ts.map +1 -0
- package/dist/internal/model.js +49 -0
- package/dist/internal/model.js.map +1 -0
- package/dist/internal/native-publication.d.ts +9 -0
- package/dist/internal/native-publication.d.ts.map +1 -0
- package/dist/internal/native-publication.js +65 -0
- package/dist/internal/native-publication.js.map +1 -0
- package/dist/internal/publication.d.ts +10 -0
- package/dist/internal/publication.d.ts.map +1 -0
- package/dist/internal/publication.js +223 -0
- package/dist/internal/publication.js.map +1 -0
- package/dist/lease.d.ts +24 -0
- package/dist/lease.d.ts.map +1 -0
- package/dist/lease.js +932 -0
- package/dist/lease.js.map +1 -0
- package/dist/limits.d.ts +15 -0
- package/dist/limits.d.ts.map +1 -0
- package/dist/limits.js +14 -0
- package/dist/limits.js.map +1 -0
- package/dist/paths.d.ts +15 -0
- package/dist/paths.d.ts.map +1 -0
- package/dist/paths.js +58 -0
- package/dist/paths.js.map +1 -0
- package/dist/revision.d.ts +4 -0
- package/dist/revision.d.ts.map +1 -0
- package/dist/revision.js +8 -0
- package/dist/revision.js.map +1 -0
- package/dist/sqlite/adapter.d.ts +48 -0
- package/dist/sqlite/adapter.d.ts.map +1 -0
- package/dist/sqlite/adapter.js +183 -0
- package/dist/sqlite/adapter.js.map +1 -0
- package/dist/sqlite/bun.d.ts +2 -0
- package/dist/sqlite/bun.d.ts.map +1 -0
- package/dist/sqlite/bun.js +7 -0
- package/dist/sqlite/bun.js.map +1 -0
- package/dist/sqlite/cache-publication.d.ts +6 -0
- package/dist/sqlite/cache-publication.d.ts.map +1 -0
- package/dist/sqlite/cache-publication.js +191 -0
- package/dist/sqlite/cache-publication.js.map +1 -0
- package/dist/sqlite/cache.d.ts +26 -0
- package/dist/sqlite/cache.d.ts.map +1 -0
- package/dist/sqlite/cache.js +527 -0
- package/dist/sqlite/cache.js.map +1 -0
- package/dist/sqlite/node.d.ts +2 -0
- package/dist/sqlite/node.d.ts.map +1 -0
- package/dist/sqlite/node.js +6 -0
- package/dist/sqlite/node.js.map +1 -0
- package/dist/sqlite/runtime.d.ts +4 -0
- package/dist/sqlite/runtime.d.ts.map +1 -0
- package/dist/sqlite/runtime.js +34 -0
- package/dist/sqlite/runtime.js.map +1 -0
- package/dist/testing.d.ts +2 -0
- package/dist/testing.d.ts.map +1 -0
- package/dist/testing.js +2 -0
- package/dist/testing.js.map +1 -0
- package/dist/transaction/apply.d.ts +8 -0
- package/dist/transaction/apply.d.ts.map +1 -0
- package/dist/transaction/apply.js +273 -0
- package/dist/transaction/apply.js.map +1 -0
- package/dist/transaction/journal.d.ts +17 -0
- package/dist/transaction/journal.d.ts.map +1 -0
- package/dist/transaction/journal.js +207 -0
- package/dist/transaction/journal.js.map +1 -0
- package/dist/transaction/recovery.d.ts +6 -0
- package/dist/transaction/recovery.d.ts.map +1 -0
- package/dist/transaction/recovery.js +138 -0
- package/dist/transaction/recovery.js.map +1 -0
- package/dist/transaction/types.d.ts +45 -0
- package/dist/transaction/types.d.ts.map +1 -0
- package/dist/transaction/types.js +2 -0
- package/dist/transaction/types.js.map +1 -0
- package/native/rename_noreplace.cc +145 -0
- package/package.json +71 -0
- package/scripts/install-native.mjs +10 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { lstatSync, readdirSync, renameSync } from 'node:fs';
|
|
2
|
+
import { dirname, join } from 'node:path';
|
|
3
|
+
import { RecoveryError } from '../errors.js';
|
|
4
|
+
import { readManagedFileIfExists } from '../files.js';
|
|
5
|
+
import { publishTransition } from './apply.js';
|
|
6
|
+
import { cleanupIncompleteJournal, cleanupJournal, readArtifact, readManifest, transactionRoot, validateArtifacts, } from './journal.js';
|
|
7
|
+
import { emitTransactionFault } from '../internal/fault-injection.js';
|
|
8
|
+
import { assertSafeRegular, syncDirectory } from '../internal/filesystem.js';
|
|
9
|
+
import { getPathState, getRootState } from '../internal/model.js';
|
|
10
|
+
import { recoverExactPublication } from '../internal/publication.js';
|
|
11
|
+
import { assertLiveLease, checkControl } from '../lease.js';
|
|
12
|
+
import { resolveManagedPath, validateRelativePath } from '../paths.js';
|
|
13
|
+
const STATE_NAME = /^([0-9a-f]{64})\.(prepare|active|committed|cleanup)$/u;
|
|
14
|
+
export async function recoverCanonicalTransactions(root, lease, options = {}) {
|
|
15
|
+
assertLiveLease(root, lease);
|
|
16
|
+
checkControl(options);
|
|
17
|
+
const state = requireRootState(root);
|
|
18
|
+
const transactions = transactionRoot(state.authorityRoot, state.managedRootId);
|
|
19
|
+
const report = { rolledBack: [], cleanedPrepared: [], cleanedCommitted: [] };
|
|
20
|
+
for (const name of readdirSync(transactions).sort()) {
|
|
21
|
+
checkControl(options);
|
|
22
|
+
const match = STATE_NAME.exec(name);
|
|
23
|
+
if (match === null)
|
|
24
|
+
continue;
|
|
25
|
+
const transactionId = match[1];
|
|
26
|
+
const phase = match[2];
|
|
27
|
+
const directory = join(transactions, name);
|
|
28
|
+
const stat = lstatSync(directory);
|
|
29
|
+
if (stat.isSymbolicLink() || !stat.isDirectory())
|
|
30
|
+
throw malformed(`Unsafe transaction state path: ${directory}`);
|
|
31
|
+
if (phase === 'prepare' || phase === 'cleanup') {
|
|
32
|
+
cleanupIncompleteJournal(directory, transactionId, {
|
|
33
|
+
maxJournalBytes: state.limits.maxJournalBytes,
|
|
34
|
+
maxArtifactBytes: Math.max(state.limits.maxBeforeImageBytes, state.limits.maxTemporaryBytes),
|
|
35
|
+
});
|
|
36
|
+
if (phase === 'prepare')
|
|
37
|
+
report.cleanedPrepared.push(transactionId);
|
|
38
|
+
else
|
|
39
|
+
report.cleanedCommitted.push(transactionId);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
const manifest = readAndValidateManifest(root, directory, transactionId);
|
|
43
|
+
if (phase === 'active') {
|
|
44
|
+
await recoverActiveDirectory(root, lease, directory, options);
|
|
45
|
+
report.rolledBack.push(manifest.transactionId);
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
const cleanup = transitionToCleanup(directory, transactionId);
|
|
49
|
+
cleanupJournal(cleanup, manifest);
|
|
50
|
+
report.cleanedCommitted.push(manifest.transactionId);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return report;
|
|
54
|
+
}
|
|
55
|
+
export async function recoverActiveDirectory(root, lease, directory, options) {
|
|
56
|
+
const state = requireRootState(root);
|
|
57
|
+
const transactionId = directory.split(/[\\/]/u).at(-1)?.split('.')[0];
|
|
58
|
+
if (transactionId === undefined)
|
|
59
|
+
throw malformed('Active transaction path has no identity.');
|
|
60
|
+
const manifest = readAndValidateManifest(root, directory, transactionId);
|
|
61
|
+
for (const entry of [...manifest.entries].reverse()) {
|
|
62
|
+
checkControl(options);
|
|
63
|
+
const path = resolveManagedPath(root, entry.path);
|
|
64
|
+
const pathState = getPathState(path);
|
|
65
|
+
if (pathState === undefined)
|
|
66
|
+
throw malformed('Recovered path is not a repository-store handle.');
|
|
67
|
+
recoverExactPublication(pathState.absolutePath, manifest.transactionId, [entry.originalRevision, entry.intendedRevision], state.limits.maxFileBytes);
|
|
68
|
+
const current = await readManagedFileIfExists(root, lease, path, options);
|
|
69
|
+
const revision = current?.revision ?? null;
|
|
70
|
+
if (revision === entry.originalRevision)
|
|
71
|
+
continue;
|
|
72
|
+
if (revision !== entry.intendedRevision)
|
|
73
|
+
throw new RecoveryError('Canonical state differs from both journal revisions.', 'RECOVERY_AMBIGUOUS', {
|
|
74
|
+
transactionId: manifest.transactionId,
|
|
75
|
+
path: entry.path,
|
|
76
|
+
currentRevision: revision,
|
|
77
|
+
originalRevision: entry.originalRevision,
|
|
78
|
+
intendedRevision: entry.intendedRevision,
|
|
79
|
+
});
|
|
80
|
+
const original = entry.beforeArtifact === null
|
|
81
|
+
? null
|
|
82
|
+
: readArtifact(directory, entry.beforeArtifact, state.limits.maxBeforeImageBytes);
|
|
83
|
+
const identity = current === undefined ? undefined : lstatSync(pathState.absolutePath);
|
|
84
|
+
if (identity !== undefined)
|
|
85
|
+
assertSafeRegular(identity, pathState.absolutePath);
|
|
86
|
+
publishTransition(pathState.absolutePath, original, manifest.transactionId, current === undefined
|
|
87
|
+
? undefined
|
|
88
|
+
: {
|
|
89
|
+
identity: identity,
|
|
90
|
+
revision: current.revision,
|
|
91
|
+
maxBytes: state.limits.maxFileBytes,
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
for (const entry of manifest.entries) {
|
|
95
|
+
const path = resolveManagedPath(root, entry.path);
|
|
96
|
+
const current = await readManagedFileIfExists(root, lease, path, options);
|
|
97
|
+
if ((current?.revision ?? null) !== entry.originalRevision)
|
|
98
|
+
throw new RecoveryError('Rollback verification did not restore original revision.', 'RECOVERY_AMBIGUOUS', {
|
|
99
|
+
transactionId: manifest.transactionId,
|
|
100
|
+
path: entry.path,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
const cleanup = transitionToCleanup(directory, manifest.transactionId);
|
|
104
|
+
cleanupJournal(cleanup, manifest);
|
|
105
|
+
}
|
|
106
|
+
function readAndValidateManifest(root, directory, transactionId) {
|
|
107
|
+
const state = requireRootState(root);
|
|
108
|
+
const manifest = readManifest(directory, state.limits.maxJournalBytes);
|
|
109
|
+
if (manifest.transactionId !== transactionId ||
|
|
110
|
+
manifest.authorityId !== state.authorityId ||
|
|
111
|
+
manifest.managedRootId !== state.managedRootId)
|
|
112
|
+
throw malformed('Transaction identity does not match its authority, managed root, or directory name.');
|
|
113
|
+
for (const entry of manifest.entries)
|
|
114
|
+
validateRelativePath(entry.path);
|
|
115
|
+
validateArtifacts(directory, manifest, {
|
|
116
|
+
maxBeforeImageBytes: state.limits.maxBeforeImageBytes,
|
|
117
|
+
maxTemporaryBytes: state.limits.maxTemporaryBytes,
|
|
118
|
+
});
|
|
119
|
+
return manifest;
|
|
120
|
+
}
|
|
121
|
+
function transitionToCleanup(directory, transactionId) {
|
|
122
|
+
const cleanup = join(dirname(directory), `${transactionId}.cleanup`);
|
|
123
|
+
renameSync(directory, cleanup);
|
|
124
|
+
emitTransactionFault('cleanup-state-renamed');
|
|
125
|
+
syncDirectory(dirname(directory));
|
|
126
|
+
emitTransactionFault('cleanup-state-synced');
|
|
127
|
+
return cleanup;
|
|
128
|
+
}
|
|
129
|
+
function requireRootState(root) {
|
|
130
|
+
const state = getRootState(root);
|
|
131
|
+
if (state === undefined)
|
|
132
|
+
throw malformed('Managed root is not a repository-store handle.');
|
|
133
|
+
return state;
|
|
134
|
+
}
|
|
135
|
+
function malformed(message) {
|
|
136
|
+
return new RecoveryError(message, 'RECOVERY_MALFORMED');
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=recovery.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recovery.js","sourceRoot":"","sources":["../../src/transaction/recovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { ManagedPath } from '../internal/model.js';
|
|
2
|
+
import type { OperationControl } from '../lease.js';
|
|
3
|
+
import type { ByteRevision } from '../revision.js';
|
|
4
|
+
export type CanonicalOperation = {
|
|
5
|
+
readonly kind: 'write';
|
|
6
|
+
readonly path: ManagedPath;
|
|
7
|
+
readonly bytes: Uint8Array;
|
|
8
|
+
readonly expected: 'absent' | ByteRevision;
|
|
9
|
+
} | {
|
|
10
|
+
readonly kind: 'remove';
|
|
11
|
+
readonly path: ManagedPath;
|
|
12
|
+
readonly expected: ByteRevision;
|
|
13
|
+
} | {
|
|
14
|
+
readonly kind: 'move';
|
|
15
|
+
readonly from: ManagedPath;
|
|
16
|
+
readonly to: ManagedPath;
|
|
17
|
+
readonly expectedSource: ByteRevision;
|
|
18
|
+
readonly expectedDestination: 'absent' | ByteRevision;
|
|
19
|
+
};
|
|
20
|
+
export interface ApplyCanonicalBatchOptions extends OperationControl {
|
|
21
|
+
readonly inventory?: readonly ManagedPath[];
|
|
22
|
+
}
|
|
23
|
+
export interface RecoveryReport {
|
|
24
|
+
readonly rolledBack: readonly string[];
|
|
25
|
+
readonly cleanedPrepared: readonly string[];
|
|
26
|
+
readonly cleanedCommitted: readonly string[];
|
|
27
|
+
}
|
|
28
|
+
export interface JournalEntry {
|
|
29
|
+
readonly path: string;
|
|
30
|
+
readonly originalRevision: ByteRevision | null;
|
|
31
|
+
readonly intendedRevision: ByteRevision | null;
|
|
32
|
+
readonly beforeArtifact: string | null;
|
|
33
|
+
readonly stagedArtifact: string | null;
|
|
34
|
+
readonly bytes: number;
|
|
35
|
+
}
|
|
36
|
+
export interface JournalManifest {
|
|
37
|
+
readonly version: 1;
|
|
38
|
+
readonly transactionId: string;
|
|
39
|
+
readonly authorityId: string;
|
|
40
|
+
readonly managedRootId: string;
|
|
41
|
+
readonly entries: readonly JournalEntry[];
|
|
42
|
+
readonly createdAt: string;
|
|
43
|
+
readonly digest: string;
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/transaction/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AACpD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAGnD,MAAM,MAAM,kBAAkB,GAC1B;IACE,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,GAAG,YAAY,CAAC;CAC5C,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,QAAQ,EAAE,YAAY,CAAC;CACjC,GACD;IACE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAC3B,QAAQ,CAAC,EAAE,EAAE,WAAW,CAAC;IACzB,QAAQ,CAAC,cAAc,EAAE,YAAY,CAAC;IACtC,QAAQ,CAAC,mBAAmB,EAAE,QAAQ,GAAG,YAAY,CAAC;CACvD,CAAC;AAGN,MAAM,WAAW,0BAA2B,SAAQ,gBAAgB;IAElE,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,WAAW,EAAE,CAAC;CAC7C;AAGD,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,CAAC;IACvC,QAAQ,CAAC,eAAe,EAAE,SAAS,MAAM,EAAE,CAAC;IAC5C,QAAQ,CAAC,gBAAgB,EAAE,SAAS,MAAM,EAAE,CAAC;CAC9C;AAGD,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,gBAAgB,EAAE,YAAY,GAAG,IAAI,CAAC;IAC/C,QAAQ,CAAC,gBAAgB,EAAE,YAAY,GAAG,IAAI,CAAC;IAC/C,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,cAAc,EAAE,MAAM,GAAG,IAAI,CAAC;IACvC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AAGD,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC;IACpB,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,OAAO,EAAE,SAAS,YAAY,EAAE,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/transaction/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
#include <node_api.h>
|
|
2
|
+
|
|
3
|
+
#include <cerrno>
|
|
4
|
+
#include <cstring>
|
|
5
|
+
|
|
6
|
+
#ifdef __linux__
|
|
7
|
+
#include <fcntl.h>
|
|
8
|
+
#include <linux/fs.h>
|
|
9
|
+
#include <linux/magic.h>
|
|
10
|
+
#include <stdio.h>
|
|
11
|
+
#include <sys/vfs.h>
|
|
12
|
+
#endif
|
|
13
|
+
|
|
14
|
+
namespace {
|
|
15
|
+
|
|
16
|
+
const char* ErrnoCode(int value) {
|
|
17
|
+
switch (value) {
|
|
18
|
+
case EEXIST:
|
|
19
|
+
return "EEXIST";
|
|
20
|
+
case ENOENT:
|
|
21
|
+
return "ENOENT";
|
|
22
|
+
case EXDEV:
|
|
23
|
+
return "EXDEV";
|
|
24
|
+
case ENOSYS:
|
|
25
|
+
return "ENOSYS";
|
|
26
|
+
case ENOTSUP:
|
|
27
|
+
return "ENOTSUP";
|
|
28
|
+
case EINVAL:
|
|
29
|
+
return "EINVAL";
|
|
30
|
+
case EPERM:
|
|
31
|
+
return "EPERM";
|
|
32
|
+
case EACCES:
|
|
33
|
+
return "EACCES";
|
|
34
|
+
default:
|
|
35
|
+
return "EIO";
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
void ThrowErrno(napi_env env, int value) {
|
|
40
|
+
napi_value message;
|
|
41
|
+
napi_create_string_utf8(env, std::strerror(value), NAPI_AUTO_LENGTH, &message);
|
|
42
|
+
napi_value error;
|
|
43
|
+
napi_create_error(env, nullptr, message, &error);
|
|
44
|
+
napi_value code;
|
|
45
|
+
napi_create_string_utf8(env, ErrnoCode(value), NAPI_AUTO_LENGTH, &code);
|
|
46
|
+
napi_set_named_property(env, error, "code", code);
|
|
47
|
+
napi_throw(env, error);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
bool ReadPath(napi_env env, napi_value value, char** output) {
|
|
51
|
+
size_t length = 0;
|
|
52
|
+
if (napi_get_value_string_utf8(env, value, nullptr, 0, &length) != napi_ok) return false;
|
|
53
|
+
*output = new char[length + 1];
|
|
54
|
+
size_t copied = 0;
|
|
55
|
+
if (napi_get_value_string_utf8(env, value, *output, length + 1, &copied) != napi_ok) {
|
|
56
|
+
delete[] *output;
|
|
57
|
+
*output = nullptr;
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
napi_value RenameNoReplace(napi_env env, napi_callback_info info) {
|
|
64
|
+
size_t argc = 2;
|
|
65
|
+
napi_value argv[2];
|
|
66
|
+
napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
|
|
67
|
+
if (argc != 2) {
|
|
68
|
+
napi_throw_type_error(env, nullptr, "renameNoReplace requires source and destination paths");
|
|
69
|
+
return nullptr;
|
|
70
|
+
}
|
|
71
|
+
char* source = nullptr;
|
|
72
|
+
char* destination = nullptr;
|
|
73
|
+
if (!ReadPath(env, argv[0], &source) || !ReadPath(env, argv[1], &destination)) {
|
|
74
|
+
delete[] source;
|
|
75
|
+
delete[] destination;
|
|
76
|
+
napi_throw_type_error(env, nullptr, "renameNoReplace paths must be strings");
|
|
77
|
+
return nullptr;
|
|
78
|
+
}
|
|
79
|
+
#ifdef __linux__
|
|
80
|
+
const int result = renameat2(AT_FDCWD, source, AT_FDCWD, destination, RENAME_NOREPLACE);
|
|
81
|
+
const int saved_errno = errno;
|
|
82
|
+
#else
|
|
83
|
+
const int result = -1;
|
|
84
|
+
const int saved_errno = ENOSYS;
|
|
85
|
+
#endif
|
|
86
|
+
delete[] source;
|
|
87
|
+
delete[] destination;
|
|
88
|
+
if (result != 0) {
|
|
89
|
+
ThrowErrno(env, saved_errno);
|
|
90
|
+
return nullptr;
|
|
91
|
+
}
|
|
92
|
+
napi_value undefined;
|
|
93
|
+
napi_get_undefined(env, &undefined);
|
|
94
|
+
return undefined;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
napi_value SupportsLocalPath(napi_env env, napi_callback_info info) {
|
|
98
|
+
size_t argc = 1;
|
|
99
|
+
napi_value argv[1];
|
|
100
|
+
napi_get_cb_info(env, info, &argc, argv, nullptr, nullptr);
|
|
101
|
+
char* path = nullptr;
|
|
102
|
+
if (argc != 1 || !ReadPath(env, argv[0], &path)) {
|
|
103
|
+
napi_throw_type_error(env, nullptr, "supportsLocalPath requires one path");
|
|
104
|
+
return nullptr;
|
|
105
|
+
}
|
|
106
|
+
bool supported = false;
|
|
107
|
+
#ifdef __linux__
|
|
108
|
+
struct statfs state;
|
|
109
|
+
if (statfs(path, &state) != 0) {
|
|
110
|
+
const int saved_errno = errno;
|
|
111
|
+
delete[] path;
|
|
112
|
+
ThrowErrno(env, saved_errno);
|
|
113
|
+
return nullptr;
|
|
114
|
+
}
|
|
115
|
+
switch (state.f_type) {
|
|
116
|
+
case EXT4_SUPER_MAGIC:
|
|
117
|
+
case XFS_SUPER_MAGIC:
|
|
118
|
+
case BTRFS_SUPER_MAGIC:
|
|
119
|
+
case TMPFS_MAGIC:
|
|
120
|
+
case OVERLAYFS_SUPER_MAGIC:
|
|
121
|
+
supported = true;
|
|
122
|
+
break;
|
|
123
|
+
default:
|
|
124
|
+
supported = false;
|
|
125
|
+
}
|
|
126
|
+
#endif
|
|
127
|
+
delete[] path;
|
|
128
|
+
napi_value result;
|
|
129
|
+
napi_get_boolean(env, supported, &result);
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
napi_value Initialize(napi_env env, napi_value exports) {
|
|
134
|
+
napi_value rename_function;
|
|
135
|
+
napi_create_function(env, "renameNoReplace", NAPI_AUTO_LENGTH, RenameNoReplace, nullptr, &rename_function);
|
|
136
|
+
napi_set_named_property(env, exports, "renameNoReplace", rename_function);
|
|
137
|
+
napi_value supports_function;
|
|
138
|
+
napi_create_function(env, "supportsLocalPath", NAPI_AUTO_LENGTH, SupportsLocalPath, nullptr, &supports_function);
|
|
139
|
+
napi_set_named_property(env, exports, "supportsLocalPath", supports_function);
|
|
140
|
+
return exports;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
} // namespace
|
|
144
|
+
|
|
145
|
+
NAPI_MODULE(NODE_GYP_MODULE_NAME, Initialize)
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@neottia/repository-store",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Crash-recoverable repository-local canonical file and disposable SQLite storage primitives",
|
|
5
|
+
"author": "Dragos Cirjan",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"sideEffects": false,
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/dragoscirjan/neottia.git",
|
|
12
|
+
"directory": "packages/repository-store"
|
|
13
|
+
},
|
|
14
|
+
"bugs": {
|
|
15
|
+
"url": "https://github.com/dragoscirjan/neottia/issues"
|
|
16
|
+
},
|
|
17
|
+
"homepage": "https://github.com/dragoscirjan/neottia#readme",
|
|
18
|
+
"publishConfig": {
|
|
19
|
+
"access": "public"
|
|
20
|
+
},
|
|
21
|
+
"main": "./dist/index.js",
|
|
22
|
+
"types": "./dist/index.d.ts",
|
|
23
|
+
"typesVersions": {
|
|
24
|
+
"*": {
|
|
25
|
+
"testing": [
|
|
26
|
+
"dist/testing.d.ts"
|
|
27
|
+
]
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
"exports": {
|
|
31
|
+
".": {
|
|
32
|
+
"types": "./dist/index.d.ts",
|
|
33
|
+
"default": "./dist/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./testing": {
|
|
36
|
+
"types": "./dist/testing.d.ts",
|
|
37
|
+
"default": "./dist/testing.js"
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
"files": [
|
|
41
|
+
"dist",
|
|
42
|
+
"native",
|
|
43
|
+
"scripts/install-native.mjs",
|
|
44
|
+
"binding.gyp",
|
|
45
|
+
"README.md",
|
|
46
|
+
"LICENSE"
|
|
47
|
+
],
|
|
48
|
+
"gypfile": true,
|
|
49
|
+
"engines": {
|
|
50
|
+
"node": ">=22.16.0"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"node-gyp": "^11.5.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@templ-project/tsconfig": "^0.2.2",
|
|
57
|
+
"@types/node": "^25.0.3",
|
|
58
|
+
"typescript": "^5.9.3",
|
|
59
|
+
"vitest": "^4.1.11"
|
|
60
|
+
},
|
|
61
|
+
"scripts": {
|
|
62
|
+
"build": "pnpm run build:native && node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json",
|
|
63
|
+
"build:native": "node-gyp rebuild",
|
|
64
|
+
"install": "node scripts/install-native.mjs",
|
|
65
|
+
"test": "pnpm run test:node && pnpm run test:bun && pnpm run test:cross-runtime",
|
|
66
|
+
"test:node": "pnpm run build && vitest run --root ../.. packages/repository-store/src/repository-store.spec.ts packages/repository-store/src/sqlite/cache.spec.ts packages/repository-store/src/sqlite/node-adapter.spec.ts",
|
|
67
|
+
"test:bun": "pnpm run build && bun test ./src/sqlite/bun-adapter.bun.test.ts ./src/publication.bun.test.ts",
|
|
68
|
+
"test:cross-runtime": "pnpm run build && vitest run --root ../.. packages/repository-store/src/sqlite/cross-runtime.spec.ts",
|
|
69
|
+
"typecheck": "tsc --noEmit"
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
|
|
3
|
+
if (process.platform === 'linux') {
|
|
4
|
+
const command = process.platform === 'win32' ? 'node-gyp.cmd' : 'node-gyp';
|
|
5
|
+
const result = spawnSync(command, ['rebuild'], { stdio: 'inherit', shell: true });
|
|
6
|
+
if (result.status !== 0)
|
|
7
|
+
console.warn(
|
|
8
|
+
'@neottia/repository-store: native exact-publication backend was not built; destructive filesystem mutations will fail with UNSUPPORTED_RUNTIME.',
|
|
9
|
+
);
|
|
10
|
+
}
|