local-kv-cli 1.0.0 → 2.0.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/dist/cli.js CHANGED
@@ -1,100 +1,350 @@
1
1
  #!/usr/bin/env node
2
2
  "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
3
36
  Object.defineProperty(exports, "__esModule", { value: true });
4
- exports.run = run;
37
+ exports.main = main;
38
+ const fs = __importStar(require("fs"));
39
+ const path = __importStar(require("path"));
40
+ const args_1 = require("./args");
41
+ const clipboard_1 = require("./clipboard");
42
+ const env_1 = require("./env");
43
+ const format_1 = require("./format");
44
+ const locate_1 = require("./locate");
5
45
  const store_1 = require("./store");
46
+ const values_1 = require("./values");
6
47
  const HELP = `lkv - store key-value pairs locally
7
48
 
8
49
  Usage:
9
- lkv set <key> <value> [--force] Create a pair (--force overwrites)
10
- lkv get <key> Print the value of a key
11
- lkv update <key> <value> Change the value of an existing key
12
- lkv delete <key> Remove a pair
13
- lkv list [--json] Show all pairs
14
- lkv clear Remove all pairs
15
- lkv path Show where the data file lives
16
- lkv help Show this help
50
+ lkv set <key> <value> Create a pair (--force to overwrite)
51
+ lkv get <key> Print a value (--copy to copy it, --json for JSON)
52
+ lkv update <key> <value> Change an existing value
53
+ lkv delete <key> Remove a pair (alias: rm)
54
+ lkv rename <old> <new> Rename a key (--force to overwrite) (alias: mv)
55
+ lkv incr <key> [amount] Add to a number (starts at 0)
56
+ lkv decr <key> [amount] Subtract from a number
57
+ lkv list Show all pairs (alias: ls)
58
+ --long show type and last update
59
+ --filter only keys containing some text
60
+ --json print as JSON
61
+ lkv search <text> Find pairs whose key or value contains text
62
+ lkv history <key> Show previous values of a key
63
+ lkv namespaces List namespaces (alias: ns)
64
+ lkv clear Remove all pairs in the namespace
65
+ lkv export [file] Back up as JSON (--all for every namespace)
66
+ lkv import <file> Restore a backup (--force to overwrite existing keys)
67
+ lkv env Print pairs as environment variables
68
+ --shell sh|powershell|cmd, --upper for UPPERCASE names
69
+ lkv run <command...> Run a command with the pairs as environment variables
70
+ lkv init Create a project store (.lkv.json) in this folder
71
+ lkv path Show which store file is in use
72
+
73
+ Options:
74
+ -n, --namespace <name> Work in a namespace (default: "default")
75
+ -t, --type <type> Value type for set/update: ${values_1.VALUE_TYPES.join(", ")}
76
+ -g, --global Use the global store even inside a project
77
+ -v, --version Show the version
78
+
79
+ Values like 42, true or {"a":1} are stored as numbers, booleans and JSON.
80
+ Use --type string to keep them as text. Quote values that contain spaces.
17
81
 
18
- Tip: quote values with spaces, e.g. lkv set greeting "hello world"`;
19
- function fail(message) {
20
- console.error(`Error: ${message}`);
21
- process.exit(1);
82
+ Examples:
83
+ lkv set api_url https://example.com
84
+ lkv incr build_count
85
+ lkv -n work set token abc123
86
+ eval "$(lkv env)" (bash/zsh)
87
+ lkv env | Invoke-Expression (PowerShell)`;
88
+ class CliError extends Error {
22
89
  }
23
- function requireArgs(args, count, usage) {
90
+ function need(args, count, usage) {
24
91
  if (args.length < count)
25
- fail(`Usage: lkv ${usage}`);
92
+ throw new CliError(`Usage: lkv ${usage}`);
26
93
  }
27
- function run(argv, store = new store_1.Store()) {
28
- const flags = new Set(argv.filter((a) => a.startsWith("--")));
29
- const [command, ...args] = argv.filter((a) => !a.startsWith("--"));
94
+ /** Runs the CLI and returns the exit code. */
95
+ function main(argv) {
96
+ const parsed = (0, args_1.parseArgs)(argv);
97
+ const { command, flags } = parsed;
98
+ if (flags.has("version")) {
99
+ console.log(readVersion());
100
+ return 0;
101
+ }
102
+ if (flags.has("help") || command === undefined || command === "help") {
103
+ console.log(HELP);
104
+ return 0;
105
+ }
106
+ if (command === "init")
107
+ return init();
108
+ const store = new store_1.Store({ namespace: parsed.options.namespace, global: flags.has("global") });
109
+ try {
110
+ return runCommand(store, parsed);
111
+ }
112
+ finally {
113
+ if (store.migratedBackup) {
114
+ console.error(`Note: upgraded your data to the v2 format (backup saved to ${store.migratedBackup})`);
115
+ }
116
+ }
117
+ }
118
+ function runCommand(store, { command, args, options, flags }) {
30
119
  switch (command) {
31
120
  case "set": {
32
- requireArgs(args, 2, "set <key> <value>");
121
+ need(args, 2, "set <key> <value>");
33
122
  const [key, ...rest] = args;
34
- store.set(key, rest.join(" "), flags.has("--force"));
123
+ store.set(key, readValue(rest, options.type), { force: flags.has("force") });
35
124
  console.log(`Saved "${key}"`);
36
- break;
125
+ return 0;
37
126
  }
38
127
  case "get": {
39
- requireArgs(args, 1, "get <key>");
128
+ need(args, 1, "get <key>");
40
129
  const value = store.get(args[0]);
41
130
  if (value === undefined)
42
- fail(`Key "${args[0]}" not found`);
43
- console.log(value);
44
- break;
131
+ throw new CliError(`Key "${args[0]}" not found`);
132
+ if (flags.has("copy")) {
133
+ (0, clipboard_1.copyToClipboard)((0, values_1.formatValue)(value));
134
+ console.error(`Copied "${args[0]}" to the clipboard`);
135
+ }
136
+ else {
137
+ console.log(flags.has("json") ? JSON.stringify(value) : (0, values_1.formatValue)(value, true));
138
+ }
139
+ return 0;
45
140
  }
46
141
  case "update": {
47
- requireArgs(args, 2, "update <key> <value>");
142
+ need(args, 2, "update <key> <value>");
48
143
  const [key, ...rest] = args;
49
- store.update(key, rest.join(" "));
144
+ store.update(key, readValue(rest, options.type));
50
145
  console.log(`Updated "${key}"`);
51
- break;
146
+ return 0;
52
147
  }
53
148
  case "delete":
54
149
  case "rm": {
55
- requireArgs(args, 1, "delete <key>");
150
+ need(args, 1, "delete <key>");
56
151
  if (!store.delete(args[0]))
57
- fail(`Key "${args[0]}" not found`);
152
+ throw new CliError(`Key "${args[0]}" not found`);
58
153
  console.log(`Deleted "${args[0]}"`);
59
- break;
154
+ return 0;
155
+ }
156
+ case "rename":
157
+ case "mv": {
158
+ need(args, 2, "rename <old> <new>");
159
+ store.rename(args[0], args[1], { force: flags.has("force") });
160
+ console.log(`Renamed "${args[0]}" to "${args[1]}"`);
161
+ return 0;
162
+ }
163
+ case "incr":
164
+ case "decr": {
165
+ need(args, 1, `${command} <key> [amount]`);
166
+ const amount = args[1] === undefined ? 1 : Number(args[1]);
167
+ if (args[1] !== undefined && (args[1].trim() === "" || !Number.isFinite(amount))) {
168
+ throw new CliError(`"${args[1]}" is not a number`);
169
+ }
170
+ // String() so console.log never colors the number (it does when FORCE_COLOR is set).
171
+ console.log(String(store.incr(args[0], command === "incr" ? amount : -amount)));
172
+ return 0;
60
173
  }
61
174
  case "list":
62
175
  case "ls": {
63
- const data = store.all();
64
- if (flags.has("--json")) {
65
- console.log(JSON.stringify(data, null, 2));
66
- break;
176
+ let data = store.all();
177
+ if (options.filter !== undefined) {
178
+ const needle = options.filter.toLowerCase();
179
+ data = pick(data, Object.keys(data).filter((k) => k.toLowerCase().includes(needle)));
180
+ }
181
+ printPairs(store, data, flags);
182
+ return 0;
183
+ }
184
+ case "search": {
185
+ need(args, 1, "search <text>");
186
+ printPairs(store, store.search(args.join(" ")), flags);
187
+ return 0;
188
+ }
189
+ case "history": {
190
+ need(args, 1, "history <key>");
191
+ const entry = store.entry(args[0]);
192
+ if (!entry)
193
+ throw new CliError(`Key "${args[0]}" not found`);
194
+ if (flags.has("json")) {
195
+ console.log(JSON.stringify(entry, null, 2));
196
+ return 0;
197
+ }
198
+ console.log(`${formatDate(entry.updated)} ${oneLine(entry.value)} (current)`);
199
+ for (const item of [...entry.history].reverse()) {
200
+ console.log(`${formatDate(item.updated)} ${oneLine(item.value)}`);
201
+ }
202
+ if (entry.history.length === 0)
203
+ console.log("(no previous values)");
204
+ return 0;
205
+ }
206
+ case "namespaces":
207
+ case "ns": {
208
+ const counts = store.namespaces();
209
+ const names = Object.keys(counts).sort();
210
+ const width = Math.max(...names.map((n) => n.length));
211
+ for (const name of names) {
212
+ const marker = name === store.namespace ? "*" : " ";
213
+ const n = counts[name];
214
+ console.log(`${marker} ${name.padEnd(width)} ${n} ${n === 1 ? "key" : "keys"}`);
215
+ }
216
+ return 0;
217
+ }
218
+ case "clear": {
219
+ const removed = store.clear();
220
+ console.log(`Removed ${removed} ${removed === 1 ? "pair" : "pairs"} from "${store.namespace}"`);
221
+ return 0;
222
+ }
223
+ case "export": {
224
+ const data = store.export(flags.has("all"));
225
+ const json = JSON.stringify(data, null, 2);
226
+ if (args[0]) {
227
+ fs.writeFileSync(args[0], json + "\n");
228
+ const count = Object.values(data.namespaces).reduce((sum, ns) => sum + Object.keys(ns).length, 0);
229
+ console.log(`Exported ${count} ${count === 1 ? "pair" : "pairs"} to ${args[0]}`);
230
+ }
231
+ else {
232
+ console.log(json);
67
233
  }
68
- const keys = Object.keys(data);
69
- if (keys.length === 0) {
70
- console.log("(empty)");
71
- break;
234
+ return 0;
235
+ }
236
+ case "import": {
237
+ need(args, 1, "import <file> (use - to read from stdin)");
238
+ const text = (0, store_1.decodeText)(fs.readFileSync(args[0] === "-" ? 0 : args[0]));
239
+ let data;
240
+ try {
241
+ data = JSON.parse(text);
242
+ }
243
+ catch {
244
+ throw new CliError(`${args[0]} is not valid JSON`);
72
245
  }
73
- const width = Math.max(...keys.map((k) => k.length));
74
- for (const key of keys)
75
- console.log(`${key.padEnd(width)} ${data[key]}`);
76
- break;
77
- }
78
- case "clear":
79
- store.clear();
80
- console.log("All pairs removed");
81
- break;
246
+ const r = store.import(data, { force: flags.has("force") });
247
+ console.log(`Imported: ${r.added} added, ${r.overwritten} overwritten, ${r.skipped} skipped`);
248
+ if (r.skipped > 0)
249
+ console.log("Use --force to overwrite existing keys.");
250
+ return 0;
251
+ }
252
+ case "env": {
253
+ const shell = (options.shell ?? (0, env_1.defaultShell)());
254
+ if (!env_1.SHELLS.includes(shell))
255
+ throw new CliError(`Unknown shell "${shell}". Use one of: ${env_1.SHELLS.join(", ")}`);
256
+ const { vars, skipped } = (0, env_1.toEnv)(store.all(), flags.has("upper"));
257
+ warnSkipped(skipped);
258
+ const script = (0, env_1.envScript)(vars, shell);
259
+ if (script)
260
+ console.log(script);
261
+ return 0;
262
+ }
263
+ case "run": {
264
+ need(args, 1, "run <command...>");
265
+ const { vars, skipped } = (0, env_1.toEnv)(store.all(), flags.has("upper"));
266
+ warnSkipped(skipped);
267
+ return (0, env_1.runWithEnv)(args, vars);
268
+ }
82
269
  case "path":
83
270
  console.log(store.path);
84
- break;
85
- case undefined:
86
- case "help":
87
- console.log(HELP);
88
- break;
271
+ console.error(`(${store.kind} store, namespace "${store.namespace}")`);
272
+ return 0;
89
273
  default:
90
- fail(`Unknown command "${command}". Run "lkv help".`);
274
+ throw new CliError(`Unknown command "${command}". Run "lkv help".`);
275
+ }
276
+ }
277
+ function init() {
278
+ const file = path.join(process.cwd(), locate_1.PROJECT_FILE);
279
+ if (fs.existsSync(file))
280
+ throw new CliError(`${locate_1.PROJECT_FILE} already exists here`);
281
+ fs.writeFileSync(file, JSON.stringify((0, format_1.emptyFile)(), null, 2));
282
+ console.log(`Created ${file}`);
283
+ console.log(`lkv commands run in this folder (and below) now use it. Use --global for the global store.`);
284
+ console.log(`Tip: add ${locate_1.PROJECT_FILE} to .gitignore if it will hold private values.`);
285
+ return 0;
286
+ }
287
+ function readValue(words, type) {
288
+ if (type !== undefined && !(0, values_1.isValueType)(type)) {
289
+ throw new CliError(`Unknown type "${type}". Use one of: ${values_1.VALUE_TYPES.join(", ")}`);
91
290
  }
291
+ return (0, values_1.parseValue)(words.join(" "), type);
292
+ }
293
+ function printPairs(store, data, flags) {
294
+ if (flags.has("json")) {
295
+ console.log(JSON.stringify(data, null, 2));
296
+ return;
297
+ }
298
+ const keys = Object.keys(data);
299
+ if (keys.length === 0) {
300
+ console.log("(empty)");
301
+ return;
302
+ }
303
+ const width = Math.max(...keys.map((k) => k.length));
304
+ if (!flags.has("long")) {
305
+ for (const key of keys)
306
+ console.log(`${key.padEnd(width)} ${oneLine(data[key])}`);
307
+ return;
308
+ }
309
+ const entries = store.entries();
310
+ const keyW = Math.max(width, 3);
311
+ console.log(`${"KEY".padEnd(keyW)} TYPE UPDATED VALUE`);
312
+ for (const key of keys) {
313
+ const e = entries[key];
314
+ console.log(`${key.padEnd(keyW)} ${e.type.padEnd(7)} ${formatDate(e.updated)} ${oneLine(e.value)}`);
315
+ }
316
+ }
317
+ function pick(data, keys) {
318
+ const out = Object.create(null);
319
+ for (const key of keys)
320
+ out[key] = data[key];
321
+ return out;
322
+ }
323
+ function oneLine(value) {
324
+ return (0, values_1.formatValue)(value).replace(/\r?\n/g, "\\n");
325
+ }
326
+ function formatDate(iso) {
327
+ const d = new Date(iso);
328
+ if (isNaN(d.getTime()))
329
+ return iso;
330
+ const p = (n) => String(n).padStart(2, "0");
331
+ return `${d.getFullYear()}-${p(d.getMonth() + 1)}-${p(d.getDate())} ${p(d.getHours())}:${p(d.getMinutes())}`;
332
+ }
333
+ function warnSkipped(keys) {
334
+ if (keys.length > 0) {
335
+ console.error(`Skipped keys that aren't valid variable names: ${keys.join(", ")}`);
336
+ }
337
+ }
338
+ function readVersion() {
339
+ const pkg = JSON.parse(fs.readFileSync(path.join(__dirname, "..", "package.json"), "utf8"));
340
+ return pkg.version;
92
341
  }
93
342
  if (require.main === module) {
94
343
  try {
95
- run(process.argv.slice(2));
344
+ process.exitCode = main(process.argv.slice(2));
96
345
  }
97
346
  catch (err) {
98
- fail(err instanceof Error ? err.message : String(err));
347
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
348
+ process.exitCode = 1;
99
349
  }
100
350
  }
@@ -0,0 +1,2 @@
1
+ /** Copy text to the system clipboard using the OS's own tool. */
2
+ export declare function copyToClipboard(text: string): void;
@@ -0,0 +1,24 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.copyToClipboard = copyToClipboard;
4
+ const child_process_1 = require("child_process");
5
+ /** Copy text to the system clipboard using the OS's own tool. */
6
+ function copyToClipboard(text) {
7
+ const attempts = [];
8
+ if (process.platform === "win32") {
9
+ // clip.exe detects UTF-16 input (a byte order mark would end up in the clipboard).
10
+ attempts.push(["clip", [], Buffer.from(text, "utf16le")]);
11
+ }
12
+ else if (process.platform === "darwin") {
13
+ attempts.push(["pbcopy", [], Buffer.from(text)]);
14
+ }
15
+ else {
16
+ attempts.push(["wl-copy", [], Buffer.from(text)], ["xclip", ["-selection", "clipboard"], Buffer.from(text)], ["xsel", ["--clipboard", "--input"], Buffer.from(text)]);
17
+ }
18
+ for (const [cmd, args, input] of attempts) {
19
+ const result = (0, child_process_1.spawnSync)(cmd, args, { input });
20
+ if (!result.error && result.status === 0)
21
+ return;
22
+ }
23
+ throw new Error("Could not access the clipboard (on Linux, install wl-clipboard, xclip or xsel)");
24
+ }
package/dist/env.d.ts ADDED
@@ -0,0 +1,13 @@
1
+ import { Value } from "./values";
2
+ export type Shell = "sh" | "powershell" | "cmd";
3
+ export declare const SHELLS: readonly Shell[];
4
+ export declare function defaultShell(): Shell;
5
+ /** Turn stored pairs into environment variables. Keys that aren't valid names are reported. */
6
+ export declare function toEnv(data: Record<string, Value>, upper?: boolean): {
7
+ vars: Record<string, string>;
8
+ skipped: string[];
9
+ };
10
+ /** Lines that set the variables when evaluated by the given shell. */
11
+ export declare function envScript(vars: Record<string, string>, shell: Shell): string;
12
+ /** Run a command with extra environment variables. Returns its exit code. */
13
+ export declare function runWithEnv(command: string[], vars: Record<string, string>): number;
package/dist/env.js ADDED
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.SHELLS = void 0;
4
+ exports.defaultShell = defaultShell;
5
+ exports.toEnv = toEnv;
6
+ exports.envScript = envScript;
7
+ exports.runWithEnv = runWithEnv;
8
+ const child_process_1 = require("child_process");
9
+ const values_1 = require("./values");
10
+ exports.SHELLS = ["sh", "powershell", "cmd"];
11
+ const ENV_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
12
+ function defaultShell() {
13
+ return process.platform === "win32" ? "powershell" : "sh";
14
+ }
15
+ /** Turn stored pairs into environment variables. Keys that aren't valid names are reported. */
16
+ function toEnv(data, upper = false) {
17
+ const vars = {};
18
+ const skipped = [];
19
+ for (const key of Object.keys(data)) {
20
+ const name = upper ? key.toUpperCase() : key;
21
+ if (ENV_NAME.test(name))
22
+ vars[name] = (0, values_1.formatValue)(data[key]);
23
+ else
24
+ skipped.push(key);
25
+ }
26
+ return { vars, skipped };
27
+ }
28
+ /** Lines that set the variables when evaluated by the given shell. */
29
+ function envScript(vars, shell) {
30
+ return Object.keys(vars)
31
+ .map((name) => {
32
+ const value = vars[name];
33
+ switch (shell) {
34
+ case "sh":
35
+ return `export ${name}='${value.replace(/'/g, `'\\''`)}'`;
36
+ case "powershell":
37
+ return `$env:${name} = '${value.replace(/'/g, "''")}'`;
38
+ case "cmd":
39
+ return `set "${name}=${value}"`;
40
+ }
41
+ })
42
+ .join("\n");
43
+ }
44
+ /** Run a command with extra environment variables. Returns its exit code. */
45
+ function runWithEnv(command, vars) {
46
+ // Go through the shell so things like `npm` (npm.cmd on Windows) resolve.
47
+ const line = command.map(quoteArg).join(" ");
48
+ const result = (0, child_process_1.spawnSync)(line, { stdio: "inherit", shell: true, env: { ...process.env, ...vars } });
49
+ if (result.error)
50
+ throw result.error;
51
+ return result.status ?? 1;
52
+ }
53
+ function quoteArg(arg) {
54
+ if (/^[\w@%+=:,./\\-]+$/.test(arg))
55
+ return arg;
56
+ return process.platform === "win32"
57
+ ? `"${arg.replace(/"/g, '\\"')}"`
58
+ : `'${arg.replace(/'/g, `'\\''`)}'`;
59
+ }
@@ -0,0 +1,34 @@
1
+ import { Value, ValueType } from "./values";
2
+ export declare const FORMAT_VERSION = 2;
3
+ export declare const DEFAULT_NAMESPACE = "default";
4
+ /** How many previous values are kept per key. */
5
+ export declare const HISTORY_LIMIT = 20;
6
+ export interface HistoryItem {
7
+ value: Value;
8
+ type: ValueType;
9
+ updated: string;
10
+ }
11
+ export interface Entry {
12
+ value: Value;
13
+ type: ValueType;
14
+ created: string;
15
+ updated: string;
16
+ history: HistoryItem[];
17
+ }
18
+ export type Namespace = Record<string, Entry>;
19
+ export interface StoreFile {
20
+ version: typeof FORMAT_VERSION;
21
+ namespaces: Record<string, Namespace>;
22
+ }
23
+ /** Prototype-less object, so keys like "constructor" or "__proto__" are just normal keys. */
24
+ export declare function dict<T>(source?: Record<string, T>): Record<string, T>;
25
+ export declare function emptyFile(): StoreFile;
26
+ export declare function isV2(data: unknown): data is StoreFile;
27
+ export declare function isPlainObject(x: unknown): x is Record<string, unknown>;
28
+ export declare function newEntry(value: Value, now: string): Entry;
29
+ /** Build a clean Entry from untrusted JSON (a store file or an import). */
30
+ export declare function toEntry(raw: unknown, now: string): Entry;
31
+ /** Parse a v2 file (keeps the null prototypes throughout). */
32
+ export declare function normalizeV2(data: StoreFile, now: string): StoreFile;
33
+ /** v1 files were a flat { key: "string value" } object. Values stay strings. */
34
+ export declare function migrateV1(data: Record<string, unknown>, now: string): StoreFile;
package/dist/format.js ADDED
@@ -0,0 +1,86 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.HISTORY_LIMIT = exports.DEFAULT_NAMESPACE = exports.FORMAT_VERSION = void 0;
4
+ exports.dict = dict;
5
+ exports.emptyFile = emptyFile;
6
+ exports.isV2 = isV2;
7
+ exports.isPlainObject = isPlainObject;
8
+ exports.newEntry = newEntry;
9
+ exports.toEntry = toEntry;
10
+ exports.normalizeV2 = normalizeV2;
11
+ exports.migrateV1 = migrateV1;
12
+ const values_1 = require("./values");
13
+ exports.FORMAT_VERSION = 2;
14
+ exports.DEFAULT_NAMESPACE = "default";
15
+ /** How many previous values are kept per key. */
16
+ exports.HISTORY_LIMIT = 20;
17
+ /** Prototype-less object, so keys like "constructor" or "__proto__" are just normal keys. */
18
+ function dict(source) {
19
+ const out = Object.create(null);
20
+ if (source)
21
+ for (const key of Object.keys(source))
22
+ out[key] = source[key];
23
+ return out;
24
+ }
25
+ function emptyFile() {
26
+ return { version: exports.FORMAT_VERSION, namespaces: dict({ [exports.DEFAULT_NAMESPACE]: dict() }) };
27
+ }
28
+ function isV2(data) {
29
+ return (isPlainObject(data) &&
30
+ data.version === exports.FORMAT_VERSION &&
31
+ isPlainObject(data.namespaces));
32
+ }
33
+ function isPlainObject(x) {
34
+ return typeof x === "object" && x !== null && !Array.isArray(x);
35
+ }
36
+ function newEntry(value, now) {
37
+ return { value, type: (0, values_1.typeOf)(value), created: now, updated: now, history: [] };
38
+ }
39
+ /** Build a clean Entry from untrusted JSON (a store file or an import). */
40
+ function toEntry(raw, now) {
41
+ if (!isPlainObject(raw) || !("value" in raw))
42
+ throw new Error("Invalid entry in data");
43
+ const value = raw.value;
44
+ const history = Array.isArray(raw.history)
45
+ ? raw.history
46
+ .filter((h) => isPlainObject(h) && "value" in h)
47
+ .map((h) => ({
48
+ value: h.value,
49
+ type: (0, values_1.typeOf)(h.value),
50
+ updated: typeof h.updated === "string" ? h.updated : now,
51
+ }))
52
+ : [];
53
+ return {
54
+ value,
55
+ type: (0, values_1.typeOf)(value),
56
+ created: typeof raw.created === "string" ? raw.created : now,
57
+ updated: typeof raw.updated === "string" ? raw.updated : now,
58
+ history: history.slice(-exports.HISTORY_LIMIT),
59
+ };
60
+ }
61
+ /** Parse a v2 file (keeps the null prototypes throughout). */
62
+ function normalizeV2(data, now) {
63
+ const namespaces = dict();
64
+ for (const name of Object.keys(data.namespaces)) {
65
+ const ns = data.namespaces[name];
66
+ if (!isPlainObject(ns))
67
+ continue;
68
+ const entries = dict();
69
+ for (const key of Object.keys(ns))
70
+ entries[key] = toEntry(ns[key], now);
71
+ namespaces[name] = entries;
72
+ }
73
+ if (!(exports.DEFAULT_NAMESPACE in namespaces))
74
+ namespaces[exports.DEFAULT_NAMESPACE] = dict();
75
+ return { version: exports.FORMAT_VERSION, namespaces };
76
+ }
77
+ /** v1 files were a flat { key: "string value" } object. Values stay strings. */
78
+ function migrateV1(data, now) {
79
+ const file = emptyFile();
80
+ const ns = file.namespaces[exports.DEFAULT_NAMESPACE];
81
+ for (const key of Object.keys(data)) {
82
+ const value = data[key];
83
+ ns[key] = newEntry(typeof value === "string" ? value : JSON.stringify(value), now);
84
+ }
85
+ return file;
86
+ }
package/dist/index.d.ts CHANGED
@@ -1,2 +1,7 @@
1
- export { Store, getStorePath } from "./store";
2
- export type { Data } from "./store";
1
+ export { Store } from "./store";
2
+ export type { StoreOptions, ImportResult } from "./store";
3
+ export type { Entry, HistoryItem, StoreFile } from "./format";
4
+ export { locateStore, globalStorePath, PROJECT_FILE } from "./locate";
5
+ export type { StoreLocation } from "./locate";
6
+ export { parseValue, formatValue, typeOf } from "./values";
7
+ export type { Value, ValueType } from "./values";
package/dist/index.js CHANGED
@@ -1,7 +1,14 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.getStorePath = exports.Store = void 0;
3
+ exports.typeOf = exports.formatValue = exports.parseValue = exports.PROJECT_FILE = exports.globalStorePath = exports.locateStore = exports.Store = void 0;
4
4
  // Public API, so the store can also be used from code: import { Store } from "local-kv-cli"
5
5
  var store_1 = require("./store");
6
6
  Object.defineProperty(exports, "Store", { enumerable: true, get: function () { return store_1.Store; } });
7
- Object.defineProperty(exports, "getStorePath", { enumerable: true, get: function () { return store_1.getStorePath; } });
7
+ var locate_1 = require("./locate");
8
+ Object.defineProperty(exports, "locateStore", { enumerable: true, get: function () { return locate_1.locateStore; } });
9
+ Object.defineProperty(exports, "globalStorePath", { enumerable: true, get: function () { return locate_1.globalStorePath; } });
10
+ Object.defineProperty(exports, "PROJECT_FILE", { enumerable: true, get: function () { return locate_1.PROJECT_FILE; } });
11
+ var values_1 = require("./values");
12
+ Object.defineProperty(exports, "parseValue", { enumerable: true, get: function () { return values_1.parseValue; } });
13
+ Object.defineProperty(exports, "formatValue", { enumerable: true, get: function () { return values_1.formatValue; } });
14
+ Object.defineProperty(exports, "typeOf", { enumerable: true, get: function () { return values_1.typeOf; } });