@generacy-ai/knowledge-store 0.0.0-preview-20260304013206
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 +191 -0
- package/dist/index.d.ts +47 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +50 -0
- package/dist/index.js.map +1 -0
- package/dist/manager/ContextManager.d.ts +54 -0
- package/dist/manager/ContextManager.d.ts.map +1 -0
- package/dist/manager/ContextManager.js +124 -0
- package/dist/manager/ContextManager.js.map +1 -0
- package/dist/manager/KnowledgeStoreManager.d.ts +128 -0
- package/dist/manager/KnowledgeStoreManager.d.ts.map +1 -0
- package/dist/manager/KnowledgeStoreManager.js +332 -0
- package/dist/manager/KnowledgeStoreManager.js.map +1 -0
- package/dist/manager/PatternManager.d.ts +72 -0
- package/dist/manager/PatternManager.d.ts.map +1 -0
- package/dist/manager/PatternManager.js +187 -0
- package/dist/manager/PatternManager.js.map +1 -0
- package/dist/manager/PhilosophyManager.d.ts +37 -0
- package/dist/manager/PhilosophyManager.d.ts.map +1 -0
- package/dist/manager/PhilosophyManager.js +82 -0
- package/dist/manager/PhilosophyManager.js.map +1 -0
- package/dist/manager/PrincipleManager.d.ts +74 -0
- package/dist/manager/PrincipleManager.d.ts.map +1 -0
- package/dist/manager/PrincipleManager.js +159 -0
- package/dist/manager/PrincipleManager.js.map +1 -0
- package/dist/portability/Exporter.d.ts +14 -0
- package/dist/portability/Exporter.d.ts.map +1 -0
- package/dist/portability/Exporter.js +48 -0
- package/dist/portability/Exporter.js.map +1 -0
- package/dist/portability/Importer.d.ts +18 -0
- package/dist/portability/Importer.d.ts.map +1 -0
- package/dist/portability/Importer.js +192 -0
- package/dist/portability/Importer.js.map +1 -0
- package/dist/portability/redaction.d.ts +22 -0
- package/dist/portability/redaction.d.ts.map +1 -0
- package/dist/portability/redaction.js +128 -0
- package/dist/portability/redaction.js.map +1 -0
- package/dist/storage/AuditableStorage.d.ts +44 -0
- package/dist/storage/AuditableStorage.d.ts.map +1 -0
- package/dist/storage/AuditableStorage.js +109 -0
- package/dist/storage/AuditableStorage.js.map +1 -0
- package/dist/storage/LocalFileStorage.d.ts +63 -0
- package/dist/storage/LocalFileStorage.d.ts.map +1 -0
- package/dist/storage/LocalFileStorage.js +219 -0
- package/dist/storage/LocalFileStorage.js.map +1 -0
- package/dist/storage/StorageProvider.d.ts +28 -0
- package/dist/storage/StorageProvider.d.ts.map +1 -0
- package/dist/storage/StorageProvider.js +38 -0
- package/dist/storage/StorageProvider.js.map +1 -0
- package/dist/storage/VersionedStorage.d.ts +61 -0
- package/dist/storage/VersionedStorage.d.ts.map +1 -0
- package/dist/storage/VersionedStorage.js +102 -0
- package/dist/storage/VersionedStorage.js.map +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/knowledge.d.ts +149 -0
- package/dist/types/knowledge.d.ts.map +1 -0
- package/dist/types/knowledge.js +6 -0
- package/dist/types/knowledge.js.map +1 -0
- package/dist/types/portability.d.ts +85 -0
- package/dist/types/portability.d.ts.map +1 -0
- package/dist/types/portability.js +6 -0
- package/dist/types/portability.js.map +1 -0
- package/dist/types/storage.d.ts +58 -0
- package/dist/types/storage.d.ts.map +1 -0
- package/dist/types/storage.js +6 -0
- package/dist/types/storage.js.map +1 -0
- package/dist/utils/id.d.ts +6 -0
- package/dist/utils/id.d.ts.map +1 -0
- package/dist/utils/id.js +9 -0
- package/dist/utils/id.js.map +1 -0
- package/dist/utils/index.d.ts +3 -0
- package/dist/utils/index.d.ts.map +1 -0
- package/dist/utils/index.js +3 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/timestamps.d.ts +6 -0
- package/dist/utils/timestamps.d.ts.map +1 -0
- package/dist/utils/timestamps.js +8 -0
- package/dist/utils/timestamps.js.map +1 -0
- package/dist/validation/schemas.d.ts +917 -0
- package/dist/validation/schemas.d.ts.map +1 -0
- package/dist/validation/schemas.js +143 -0
- package/dist/validation/schemas.js.map +1 -0
- package/dist/validation/validator.d.ts +29 -0
- package/dist/validation/validator.d.ts.map +1 -0
- package/dist/validation/validator.js +54 -0
- package/dist/validation/validator.js.map +1 -0
- package/package.json +49 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redaction transforms for different portability levels
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Org-specific domains that should be redacted
|
|
6
|
+
*/
|
|
7
|
+
const ORG_SPECIFIC_PATTERNS = [
|
|
8
|
+
/^company[:-]/i,
|
|
9
|
+
/^org[:-]/i,
|
|
10
|
+
/^internal[:-]/i,
|
|
11
|
+
/^proprietary[:-]/i,
|
|
12
|
+
/^confidential[:-]/i,
|
|
13
|
+
];
|
|
14
|
+
/**
|
|
15
|
+
* Check if a domain is org-specific
|
|
16
|
+
*/
|
|
17
|
+
function isOrgSpecific(domain) {
|
|
18
|
+
return ORG_SPECIFIC_PATTERNS.some((pattern) => pattern.test(domain));
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Filter out org-specific domains
|
|
22
|
+
*/
|
|
23
|
+
function filterOrgDomains(domains) {
|
|
24
|
+
return domains.filter((d) => !isOrgSpecific(d));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Transform philosophy for export
|
|
28
|
+
*/
|
|
29
|
+
export function transformPhilosophy(philosophy, level) {
|
|
30
|
+
if (level === 'abstracted') {
|
|
31
|
+
// Anonymize identity
|
|
32
|
+
return {
|
|
33
|
+
values: philosophy.values,
|
|
34
|
+
beliefs: philosophy.beliefs.map((b) => ({
|
|
35
|
+
...b,
|
|
36
|
+
domain: filterOrgDomains(b.domain),
|
|
37
|
+
})),
|
|
38
|
+
identity: {},
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
if (level === 'redacted') {
|
|
42
|
+
// Remove org-specific domains from beliefs
|
|
43
|
+
return {
|
|
44
|
+
values: philosophy.values,
|
|
45
|
+
beliefs: philosophy.beliefs.map((b) => ({
|
|
46
|
+
...b,
|
|
47
|
+
domain: filterOrgDomains(b.domain),
|
|
48
|
+
})),
|
|
49
|
+
identity: philosophy.identity,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
// Full - return as-is
|
|
53
|
+
return philosophy;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Transform principles for export
|
|
57
|
+
*/
|
|
58
|
+
export function transformPrinciples(principles, level) {
|
|
59
|
+
return principles
|
|
60
|
+
.filter((p) => p.status !== 'deprecated')
|
|
61
|
+
.map((p) => {
|
|
62
|
+
const domains = level === 'full' ? p.domain : filterOrgDomains(p.domain);
|
|
63
|
+
// Skip principles that are entirely org-specific
|
|
64
|
+
if (domains.length === 0 && p.domain.length > 0) {
|
|
65
|
+
return null;
|
|
66
|
+
}
|
|
67
|
+
const exported = {
|
|
68
|
+
id: p.id,
|
|
69
|
+
content: p.content,
|
|
70
|
+
domain: domains,
|
|
71
|
+
weight: p.weight,
|
|
72
|
+
evidenceCount: p.evidence.length,
|
|
73
|
+
};
|
|
74
|
+
if (level === 'full') {
|
|
75
|
+
exported.evidence = p.evidence;
|
|
76
|
+
}
|
|
77
|
+
else if (level === 'redacted') {
|
|
78
|
+
// Include evidence but redact context if it contains org info
|
|
79
|
+
exported.evidence = p.evidence.map((e) => ({
|
|
80
|
+
...e,
|
|
81
|
+
context: e.context.replace(/\b(company|org|internal)\b/gi, '[REDACTED]'),
|
|
82
|
+
}));
|
|
83
|
+
}
|
|
84
|
+
// Abstracted - no evidence included
|
|
85
|
+
return exported;
|
|
86
|
+
})
|
|
87
|
+
.filter((p) => p !== null);
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Transform patterns for export
|
|
91
|
+
*/
|
|
92
|
+
export function transformPatterns(patterns, level) {
|
|
93
|
+
if (level === 'abstracted') {
|
|
94
|
+
// Don't include patterns in abstracted export
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
return patterns
|
|
98
|
+
.filter((p) => p.status !== 'rejected')
|
|
99
|
+
.map((p) => {
|
|
100
|
+
const domains = level === 'full' ? p.domain : filterOrgDomains(p.domain);
|
|
101
|
+
if (domains.length === 0 && p.domain.length > 0) {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
if (level === 'redacted') {
|
|
105
|
+
return {
|
|
106
|
+
...p,
|
|
107
|
+
domain: domains,
|
|
108
|
+
occurrences: p.occurrences.map((o) => ({
|
|
109
|
+
...o,
|
|
110
|
+
context: o.context.replace(/\b(company|org|internal)\b/gi, '[REDACTED]'),
|
|
111
|
+
})),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
return p;
|
|
115
|
+
})
|
|
116
|
+
.filter((p) => p !== null);
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Transform context for export
|
|
120
|
+
*/
|
|
121
|
+
export function transformContext(context, level) {
|
|
122
|
+
if (level === 'abstracted' || level === 'redacted') {
|
|
123
|
+
// Don't include context in redacted or abstracted
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
return context;
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=redaction.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"redaction.js","sourceRoot":"","sources":["../../src/portability/redaction.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auditable storage wrapper
|
|
3
|
+
* Adds audit trail logging to any StorageProvider
|
|
4
|
+
*/
|
|
5
|
+
import type { StorageProvider, VersionInfo } from '../types/storage.js';
|
|
6
|
+
/**
|
|
7
|
+
* Audit log entry
|
|
8
|
+
*/
|
|
9
|
+
export interface AuditEntry {
|
|
10
|
+
timestamp: string;
|
|
11
|
+
action: 'set' | 'delete' | 'createVersion';
|
|
12
|
+
key: string;
|
|
13
|
+
details?: Record<string, unknown>;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Auditable storage wrapper that logs all modifications
|
|
17
|
+
*/
|
|
18
|
+
export declare class AuditableStorage implements StorageProvider {
|
|
19
|
+
private readonly storage;
|
|
20
|
+
private readonly auditPath;
|
|
21
|
+
private readonly enabled;
|
|
22
|
+
constructor(storage: StorageProvider, baseDir: string, enabled?: boolean);
|
|
23
|
+
/**
|
|
24
|
+
* Append an entry to the audit log
|
|
25
|
+
*/
|
|
26
|
+
private appendAudit;
|
|
27
|
+
/**
|
|
28
|
+
* Get the audit log entries
|
|
29
|
+
*/
|
|
30
|
+
getAuditLog(): Promise<AuditEntry[]>;
|
|
31
|
+
get<T>(key: string): Promise<T | null>;
|
|
32
|
+
set<T>(key: string, value: T): Promise<void>;
|
|
33
|
+
delete(key: string): Promise<void>;
|
|
34
|
+
list(prefix: string): Promise<string[]>;
|
|
35
|
+
exists(key: string): Promise<boolean>;
|
|
36
|
+
getVersion<T>(key: string, version: number): Promise<T | null>;
|
|
37
|
+
listVersions(key: string): Promise<VersionInfo[]>;
|
|
38
|
+
createVersion(key: string): Promise<number>;
|
|
39
|
+
/**
|
|
40
|
+
* Get the underlying storage provider
|
|
41
|
+
*/
|
|
42
|
+
getUnderlyingStorage(): StorageProvider;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=AuditableStorage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuditableStorage.d.ts","sourceRoot":"","sources":["../../src/storage/AuditableStorage.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGxE;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,KAAK,GAAG,QAAQ,GAAG,eAAe,CAAC;IAC3C,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACnC;AAED;;GAEG;AACH,qBAAa,gBAAiB,YAAW,eAAe;IACtD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAkB;IAC1C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;gBAGhC,OAAO,EAAE,eAAe,EACxB,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,OAAc;IAOzB;;OAEG;YACW,WAAW;IA2BzB;;OAEG;IACG,WAAW,IAAI,OAAO,CAAC,UAAU,EAAE,CAAC;IAWpC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAItC,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAU5C,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IASlC,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAIvC,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAIrC,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAI9D,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAIjD,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;IAWjD;;OAEG;IACH,oBAAoB,IAAI,eAAe;CAGxC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Auditable storage wrapper
|
|
3
|
+
* Adds audit trail logging to any StorageProvider
|
|
4
|
+
*/
|
|
5
|
+
import { promises as fs } from 'node:fs';
|
|
6
|
+
import { join, dirname } from 'node:path';
|
|
7
|
+
import { now } from '../utils/timestamps.js';
|
|
8
|
+
/**
|
|
9
|
+
* Auditable storage wrapper that logs all modifications
|
|
10
|
+
*/
|
|
11
|
+
export class AuditableStorage {
|
|
12
|
+
storage;
|
|
13
|
+
auditPath;
|
|
14
|
+
enabled;
|
|
15
|
+
constructor(storage, baseDir, enabled = true) {
|
|
16
|
+
this.storage = storage;
|
|
17
|
+
this.auditPath = join(baseDir, 'audit.json');
|
|
18
|
+
this.enabled = enabled;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Append an entry to the audit log
|
|
22
|
+
*/
|
|
23
|
+
async appendAudit(entry) {
|
|
24
|
+
if (!this.enabled)
|
|
25
|
+
return;
|
|
26
|
+
try {
|
|
27
|
+
await fs.mkdir(dirname(this.auditPath), { recursive: true });
|
|
28
|
+
let entries = [];
|
|
29
|
+
try {
|
|
30
|
+
const data = await fs.readFile(this.auditPath, 'utf-8');
|
|
31
|
+
entries = JSON.parse(data);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// File doesn't exist or is invalid, start fresh
|
|
35
|
+
}
|
|
36
|
+
entries.push(entry);
|
|
37
|
+
// Keep only last 1000 entries
|
|
38
|
+
if (entries.length > 1000) {
|
|
39
|
+
entries = entries.slice(-1000);
|
|
40
|
+
}
|
|
41
|
+
await fs.writeFile(this.auditPath, JSON.stringify(entries, null, 2), 'utf-8');
|
|
42
|
+
}
|
|
43
|
+
catch {
|
|
44
|
+
// Audit logging should not fail the operation
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Get the audit log entries
|
|
49
|
+
*/
|
|
50
|
+
async getAuditLog() {
|
|
51
|
+
try {
|
|
52
|
+
const data = await fs.readFile(this.auditPath, 'utf-8');
|
|
53
|
+
return JSON.parse(data);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
return [];
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// StorageProvider implementation
|
|
60
|
+
async get(key) {
|
|
61
|
+
return this.storage.get(key);
|
|
62
|
+
}
|
|
63
|
+
async set(key, value) {
|
|
64
|
+
await this.storage.set(key, value);
|
|
65
|
+
await this.appendAudit({
|
|
66
|
+
timestamp: now(),
|
|
67
|
+
action: 'set',
|
|
68
|
+
key,
|
|
69
|
+
details: { type: typeof value },
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
async delete(key) {
|
|
73
|
+
await this.storage.delete(key);
|
|
74
|
+
await this.appendAudit({
|
|
75
|
+
timestamp: now(),
|
|
76
|
+
action: 'delete',
|
|
77
|
+
key,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
async list(prefix) {
|
|
81
|
+
return this.storage.list(prefix);
|
|
82
|
+
}
|
|
83
|
+
async exists(key) {
|
|
84
|
+
return this.storage.exists(key);
|
|
85
|
+
}
|
|
86
|
+
async getVersion(key, version) {
|
|
87
|
+
return this.storage.getVersion(key, version);
|
|
88
|
+
}
|
|
89
|
+
async listVersions(key) {
|
|
90
|
+
return this.storage.listVersions(key);
|
|
91
|
+
}
|
|
92
|
+
async createVersion(key) {
|
|
93
|
+
const version = await this.storage.createVersion(key);
|
|
94
|
+
await this.appendAudit({
|
|
95
|
+
timestamp: now(),
|
|
96
|
+
action: 'createVersion',
|
|
97
|
+
key,
|
|
98
|
+
details: { version },
|
|
99
|
+
});
|
|
100
|
+
return version;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Get the underlying storage provider
|
|
104
|
+
*/
|
|
105
|
+
getUnderlyingStorage() {
|
|
106
|
+
return this.storage;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=AuditableStorage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AuditableStorage.js","sourceRoot":"","sources":["../../src/storage/AuditableStorage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local file-based storage provider
|
|
3
|
+
* Implements atomic writes using temp file + rename pattern
|
|
4
|
+
*/
|
|
5
|
+
import type { StorageProvider, VersionInfo } from '../types/storage.js';
|
|
6
|
+
/**
|
|
7
|
+
* Local file storage implementation
|
|
8
|
+
* Stores data as JSON files in the filesystem
|
|
9
|
+
*/
|
|
10
|
+
export declare class LocalFileStorage implements StorageProvider {
|
|
11
|
+
private readonly baseDir;
|
|
12
|
+
constructor(baseDir: string);
|
|
13
|
+
/**
|
|
14
|
+
* Get the full file path for a key
|
|
15
|
+
*/
|
|
16
|
+
private getPath;
|
|
17
|
+
/**
|
|
18
|
+
* Get the version directory for a key
|
|
19
|
+
*/
|
|
20
|
+
private getVersionDir;
|
|
21
|
+
/**
|
|
22
|
+
* Get the version file path
|
|
23
|
+
*/
|
|
24
|
+
private getVersionPath;
|
|
25
|
+
/**
|
|
26
|
+
* Ensure a directory exists
|
|
27
|
+
*/
|
|
28
|
+
private ensureDir;
|
|
29
|
+
/**
|
|
30
|
+
* Get a value by key
|
|
31
|
+
*/
|
|
32
|
+
get<T>(key: string): Promise<T | null>;
|
|
33
|
+
/**
|
|
34
|
+
* Set a value by key using atomic write (temp file + rename)
|
|
35
|
+
*/
|
|
36
|
+
set<T>(key: string, value: T): Promise<void>;
|
|
37
|
+
/**
|
|
38
|
+
* Delete a value by key
|
|
39
|
+
*/
|
|
40
|
+
delete(key: string): Promise<void>;
|
|
41
|
+
/**
|
|
42
|
+
* List all keys with a given prefix
|
|
43
|
+
*/
|
|
44
|
+
list(prefix: string): Promise<string[]>;
|
|
45
|
+
/**
|
|
46
|
+
* Check if a key exists
|
|
47
|
+
*/
|
|
48
|
+
exists(key: string): Promise<boolean>;
|
|
49
|
+
/**
|
|
50
|
+
* Get a specific version of a value
|
|
51
|
+
*/
|
|
52
|
+
getVersion<T>(key: string, version: number): Promise<T | null>;
|
|
53
|
+
/**
|
|
54
|
+
* List all versions for a key
|
|
55
|
+
*/
|
|
56
|
+
listVersions(key: string): Promise<VersionInfo[]>;
|
|
57
|
+
/**
|
|
58
|
+
* Create a new version for a key (copies current value to version file)
|
|
59
|
+
* Returns the new version number
|
|
60
|
+
*/
|
|
61
|
+
createVersion(key: string): Promise<number>;
|
|
62
|
+
}
|
|
63
|
+
//# sourceMappingURL=LocalFileStorage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocalFileStorage.d.ts","sourceRoot":"","sources":["../../src/storage/LocalFileStorage.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAGxE;;;GAGG;AACH,qBAAa,gBAAiB,YAAW,eAAe;IACtD,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;gBAErB,OAAO,EAAE,MAAM;IAI3B;;OAEG;IACH,OAAO,CAAC,OAAO;IAIf;;OAEG;IACH,OAAO,CAAC,aAAa;IAIrB;;OAEG;IACH,OAAO,CAAC,cAAc;IAItB;;OAEG;YACW,SAAS;IAIvB;;OAEG;IACG,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAkB5C;;OAEG;IACG,GAAG,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAyBlD;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAgBxC;;OAEG;IACG,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,EAAE,CAAC;IAgC7C;;OAEG;IACG,MAAM,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAU3C;;OAEG;IACG,UAAU,CAAC,CAAC,EAAE,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC;IAkBpE;;OAEG;IACG,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IAwCvD;;;OAGG;IACG,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAsClD"}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Local file-based storage provider
|
|
3
|
+
* Implements atomic writes using temp file + rename pattern
|
|
4
|
+
*/
|
|
5
|
+
import { promises as fs } from 'node:fs';
|
|
6
|
+
import { join, dirname } from 'node:path';
|
|
7
|
+
import { StorageError } from './StorageProvider.js';
|
|
8
|
+
/**
|
|
9
|
+
* Local file storage implementation
|
|
10
|
+
* Stores data as JSON files in the filesystem
|
|
11
|
+
*/
|
|
12
|
+
export class LocalFileStorage {
|
|
13
|
+
baseDir;
|
|
14
|
+
constructor(baseDir) {
|
|
15
|
+
this.baseDir = baseDir;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* Get the full file path for a key
|
|
19
|
+
*/
|
|
20
|
+
getPath(key) {
|
|
21
|
+
return join(this.baseDir, `${key}.json`);
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Get the version directory for a key
|
|
25
|
+
*/
|
|
26
|
+
getVersionDir(key) {
|
|
27
|
+
return join(this.baseDir, 'versions', key);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get the version file path
|
|
31
|
+
*/
|
|
32
|
+
getVersionPath(key, version) {
|
|
33
|
+
return join(this.getVersionDir(key), `v${version}.json`);
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Ensure a directory exists
|
|
37
|
+
*/
|
|
38
|
+
async ensureDir(path) {
|
|
39
|
+
await fs.mkdir(dirname(path), { recursive: true });
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Get a value by key
|
|
43
|
+
*/
|
|
44
|
+
async get(key) {
|
|
45
|
+
const path = this.getPath(key);
|
|
46
|
+
try {
|
|
47
|
+
const data = await fs.readFile(path, 'utf-8');
|
|
48
|
+
return JSON.parse(data);
|
|
49
|
+
}
|
|
50
|
+
catch (error) {
|
|
51
|
+
if (error.code === 'ENOENT') {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
throw new StorageError(`Failed to read key: ${key}`, 'get', key, error);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Set a value by key using atomic write (temp file + rename)
|
|
59
|
+
*/
|
|
60
|
+
async set(key, value) {
|
|
61
|
+
const path = this.getPath(key);
|
|
62
|
+
const tempPath = `${path}.tmp.${Date.now()}`;
|
|
63
|
+
try {
|
|
64
|
+
await this.ensureDir(path);
|
|
65
|
+
const data = JSON.stringify(value, null, 2);
|
|
66
|
+
await fs.writeFile(tempPath, data, 'utf-8');
|
|
67
|
+
await fs.rename(tempPath, path);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
// Clean up temp file if it exists
|
|
71
|
+
try {
|
|
72
|
+
await fs.unlink(tempPath);
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// Ignore cleanup errors
|
|
76
|
+
}
|
|
77
|
+
throw new StorageError(`Failed to write key: ${key}`, 'set', key, error);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Delete a value by key
|
|
82
|
+
*/
|
|
83
|
+
async delete(key) {
|
|
84
|
+
const path = this.getPath(key);
|
|
85
|
+
try {
|
|
86
|
+
await fs.unlink(path);
|
|
87
|
+
}
|
|
88
|
+
catch (error) {
|
|
89
|
+
if (error.code !== 'ENOENT') {
|
|
90
|
+
throw new StorageError(`Failed to delete key: ${key}`, 'delete', key, error);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* List all keys with a given prefix
|
|
96
|
+
*/
|
|
97
|
+
async list(prefix) {
|
|
98
|
+
const searchDir = join(this.baseDir, dirname(prefix));
|
|
99
|
+
const filePrefix = prefix.split('/').pop() || '';
|
|
100
|
+
try {
|
|
101
|
+
const entries = await fs.readdir(searchDir, { withFileTypes: true });
|
|
102
|
+
return entries
|
|
103
|
+
.filter((entry) => {
|
|
104
|
+
if (!entry.isFile() || !entry.name.endsWith('.json')) {
|
|
105
|
+
return false;
|
|
106
|
+
}
|
|
107
|
+
const keyName = entry.name.replace('.json', '');
|
|
108
|
+
return keyName.startsWith(filePrefix);
|
|
109
|
+
})
|
|
110
|
+
.map((entry) => {
|
|
111
|
+
const relativePath = dirname(prefix);
|
|
112
|
+
const keyName = entry.name.replace('.json', '');
|
|
113
|
+
return relativePath === '.' ? keyName : `${relativePath}/${keyName}`;
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
if (error.code === 'ENOENT') {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
120
|
+
throw new StorageError(`Failed to list keys with prefix: ${prefix}`, 'list', prefix, error);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Check if a key exists
|
|
125
|
+
*/
|
|
126
|
+
async exists(key) {
|
|
127
|
+
const path = this.getPath(key);
|
|
128
|
+
try {
|
|
129
|
+
await fs.access(path);
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Get a specific version of a value
|
|
138
|
+
*/
|
|
139
|
+
async getVersion(key, version) {
|
|
140
|
+
const path = this.getVersionPath(key, version);
|
|
141
|
+
try {
|
|
142
|
+
const data = await fs.readFile(path, 'utf-8');
|
|
143
|
+
return JSON.parse(data);
|
|
144
|
+
}
|
|
145
|
+
catch (error) {
|
|
146
|
+
if (error.code === 'ENOENT') {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
throw new StorageError(`Failed to read version ${version} for key: ${key}`, 'getVersion', key, error);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* List all versions for a key
|
|
154
|
+
*/
|
|
155
|
+
async listVersions(key) {
|
|
156
|
+
const versionDir = this.getVersionDir(key);
|
|
157
|
+
try {
|
|
158
|
+
const entries = await fs.readdir(versionDir, { withFileTypes: true });
|
|
159
|
+
const versions = [];
|
|
160
|
+
for (const entry of entries) {
|
|
161
|
+
if (!entry.isFile() || !entry.name.startsWith('v') || !entry.name.endsWith('.json')) {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
const versionStr = entry.name.replace('v', '').replace('.json', '');
|
|
165
|
+
const version = parseInt(versionStr, 10);
|
|
166
|
+
if (isNaN(version)) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const filePath = join(versionDir, entry.name);
|
|
170
|
+
const stat = await fs.stat(filePath);
|
|
171
|
+
versions.push({
|
|
172
|
+
version,
|
|
173
|
+
timestamp: stat.mtime.toISOString(),
|
|
174
|
+
size: stat.size,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
return versions.sort((a, b) => a.version - b.version);
|
|
178
|
+
}
|
|
179
|
+
catch (error) {
|
|
180
|
+
if (error.code === 'ENOENT') {
|
|
181
|
+
return [];
|
|
182
|
+
}
|
|
183
|
+
throw new StorageError(`Failed to list versions for key: ${key}`, 'listVersions', key, error);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Create a new version for a key (copies current value to version file)
|
|
188
|
+
* Returns the new version number
|
|
189
|
+
*/
|
|
190
|
+
async createVersion(key) {
|
|
191
|
+
const current = await this.get(key);
|
|
192
|
+
if (current === null) {
|
|
193
|
+
throw new StorageError(`Cannot create version for non-existent key: ${key}`, 'createVersion', key);
|
|
194
|
+
}
|
|
195
|
+
const versions = await this.listVersions(key);
|
|
196
|
+
const newVersion = versions.length > 0
|
|
197
|
+
? Math.max(...versions.map((v) => v.version)) + 1
|
|
198
|
+
: 1;
|
|
199
|
+
const versionPath = this.getVersionPath(key, newVersion);
|
|
200
|
+
const tempPath = `${versionPath}.tmp.${Date.now()}`;
|
|
201
|
+
try {
|
|
202
|
+
await this.ensureDir(versionPath);
|
|
203
|
+
const data = JSON.stringify(current, null, 2);
|
|
204
|
+
await fs.writeFile(tempPath, data, 'utf-8');
|
|
205
|
+
await fs.rename(tempPath, versionPath);
|
|
206
|
+
return newVersion;
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
try {
|
|
210
|
+
await fs.unlink(tempPath);
|
|
211
|
+
}
|
|
212
|
+
catch {
|
|
213
|
+
// Ignore cleanup errors
|
|
214
|
+
}
|
|
215
|
+
throw new StorageError(`Failed to create version for key: ${key}`, 'createVersion', key, error);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
//# sourceMappingURL=LocalFileStorage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"LocalFileStorage.js","sourceRoot":"","sources":["../../src/storage/LocalFileStorage.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract storage provider interface
|
|
3
|
+
* Defines the contract for all storage implementations
|
|
4
|
+
*/
|
|
5
|
+
import type { StorageProvider, VersionInfo } from '../types/storage.js';
|
|
6
|
+
export type { StorageProvider, VersionInfo };
|
|
7
|
+
/**
|
|
8
|
+
* Error thrown when a storage operation fails
|
|
9
|
+
*/
|
|
10
|
+
export declare class StorageError extends Error {
|
|
11
|
+
readonly operation: string;
|
|
12
|
+
readonly key?: string | undefined;
|
|
13
|
+
readonly cause?: Error | undefined;
|
|
14
|
+
constructor(message: string, operation: string, key?: string | undefined, cause?: Error | undefined);
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Error thrown when a key is not found
|
|
18
|
+
*/
|
|
19
|
+
export declare class KeyNotFoundError extends StorageError {
|
|
20
|
+
constructor(key: string);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Error thrown when a version is not found
|
|
24
|
+
*/
|
|
25
|
+
export declare class VersionNotFoundError extends StorageError {
|
|
26
|
+
constructor(key: string, version: number);
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=StorageProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StorageProvider.d.ts","sourceRoot":"","sources":["../../src/storage/StorageProvider.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAExE,YAAY,EAAE,eAAe,EAAE,WAAW,EAAE,CAAC;AAE7C;;GAEG;AACH,qBAAa,YAAa,SAAQ,KAAK;aAGnB,SAAS,EAAE,MAAM;aACjB,GAAG,CAAC,EAAE,MAAM;aACZ,KAAK,CAAC,EAAE,KAAK;gBAH7B,OAAO,EAAE,MAAM,EACC,SAAS,EAAE,MAAM,EACjB,GAAG,CAAC,EAAE,MAAM,YAAA,EACZ,KAAK,CAAC,EAAE,KAAK,YAAA;CAKhC;AAED;;GAEG;AACH,qBAAa,gBAAiB,SAAQ,YAAY;gBACpC,GAAG,EAAE,MAAM;CAIxB;AAED;;GAEG;AACH,qBAAa,oBAAqB,SAAQ,YAAY;gBACxC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAIzC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Abstract storage provider interface
|
|
3
|
+
* Defines the contract for all storage implementations
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Error thrown when a storage operation fails
|
|
7
|
+
*/
|
|
8
|
+
export class StorageError extends Error {
|
|
9
|
+
operation;
|
|
10
|
+
key;
|
|
11
|
+
cause;
|
|
12
|
+
constructor(message, operation, key, cause) {
|
|
13
|
+
super(message);
|
|
14
|
+
this.operation = operation;
|
|
15
|
+
this.key = key;
|
|
16
|
+
this.cause = cause;
|
|
17
|
+
this.name = 'StorageError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Error thrown when a key is not found
|
|
22
|
+
*/
|
|
23
|
+
export class KeyNotFoundError extends StorageError {
|
|
24
|
+
constructor(key) {
|
|
25
|
+
super(`Key not found: ${key}`, 'get', key);
|
|
26
|
+
this.name = 'KeyNotFoundError';
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Error thrown when a version is not found
|
|
31
|
+
*/
|
|
32
|
+
export class VersionNotFoundError extends StorageError {
|
|
33
|
+
constructor(key, version) {
|
|
34
|
+
super(`Version ${version} not found for key: ${key}`, 'getVersion', key);
|
|
35
|
+
this.name = 'VersionNotFoundError';
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=StorageProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"StorageProvider.js","sourceRoot":"","sources":["../../src/storage/StorageProvider.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAMH;;GAEG;AACH,MAAM,OAAO,YAAa,SAAQ,KAAK;IAGnB;IACA;IACA;IAJlB,YACE,OAAe,EACC,SAAiB,EACjB,GAAY,EACZ,KAAa;QAE7B,KAAK,CAAC,OAAO,CAAC,CAAC;QAJC,cAAS,GAAT,SAAS,CAAQ;QACjB,QAAG,GAAH,GAAG,CAAS;QACZ,UAAK,GAAL,KAAK,CAAQ;QAG7B,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,gBAAiB,SAAQ,YAAY;IAChD,YAAY,GAAW;QACrB,KAAK,CAAC,kBAAkB,GAAG,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;QAC3C,IAAI,CAAC,IAAI,GAAG,kBAAkB,CAAC;IACjC,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,oBAAqB,SAAQ,YAAY;IACpD,YAAY,GAAW,EAAE,OAAe;QACtC,KAAK,CAAC,WAAW,OAAO,uBAAuB,GAAG,EAAE,EAAE,YAAY,EAAE,GAAG,CAAC,CAAC;QACzE,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF"}
|