@powerduck/conf-patch 0.3.3 → 0.3.4

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.
Files changed (2) hide show
  1. package/README.md +166 -147
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,17 +1,21 @@
1
1
  # @powerduck/conf-patch
2
2
 
3
- Production-grade configuration file editor with a clean two-layer architecture: a browser-safe core layer for patching JSON/JSONC/YAML strings, and a Node.js/Electron file layer with atomic writes and cross-process file locking.
4
-
5
3
  [![npm version](https://img.shields.io/npm/v/@powerduck/conf-patch)](https://www.npmjs.com/package/@powerduck/conf-patch)
6
4
  [![license](https://img.shields.io/npm/l/@powerduck/conf-patch)](https://github.com/PowerDuckie/conf-patch/blob/main/LICENSE)
5
+ [![downloads](https://img.shields.io/npm/dm/@powerduck/conf-patch)](https://www.npmjs.com/package/@powerduck/conf-patch)
7
6
 
8
- ## Links
7
+ Production-grade configuration file editor with a clean two-layer architecture: a browser-safe core layer for patching JSON/JSONC/YAML strings, and a Node.js/Electron file layer with atomic writes and cross-process file locking.
9
8
 
10
- - [Official Website](https://www.powerduck.com/opensource/conf-patch.html)
11
- - [Documentation](https://www.powerduck.com/docs/conf-patch/introduction)
12
- - [Live Demo](https://www.powerduck.com/demo/conf-patch.html)
13
- - [GitHub](https://github.com/PowerDuckie/conf-patch)
14
- - [npm](https://www.npmjs.com/package/@powerduck/conf-patch)
9
+ ---
10
+
11
+ Powerduck is an open-source developer tooling platform for teams building modern API workflows.
12
+
13
+ - **Core Layer** — Browser-safe patching for JSON, JSONC, and YAML strings with no filesystem dependency
14
+ - **File Layer** — Atomic writes with temp-file + rename, cross-process file locking for Node.js/Electron
15
+ - **RFC 6902 JSON Patch** — `add`, `replace`, and `remove` operations with strict/non-strict modes
16
+ - **OpenAPI Validation** — Built on `@powerduck/openapi-parser` with secure input handling and DoS guards
17
+ - **Comment-Preserving Edits** — JSONC edits are range-based, preserving comments and trailing commas
18
+ - **Dual ESM/CJS** — Works with `import` and `require`, with bundled TypeScript declarations
15
19
 
16
20
  ---
17
21
 
@@ -23,7 +27,7 @@ Production-grade configuration file editor with a clean two-layer architecture:
23
27
  npm install @powerduck/conf-patch
24
28
  ```
25
29
 
26
- ### Patch a JSON string (browser-safe, no filesystem)
30
+ ### Patch a JSON string (browser-safe)
27
31
 
28
32
  ```typescript
29
33
  import { patchContent } from "@powerduck/conf-patch/core";
@@ -38,45 +42,26 @@ const updated = patchContent(
38
42
  );
39
43
 
40
44
  console.log(updated);
41
- // {
42
- // "name": "app",
43
- // "version": "2.0.0",
44
- // "description": "My application"
45
- // }
46
45
  ```
47
46
 
48
47
  ### Set a value in a file (Node.js / Electron)
49
48
 
50
49
  ```typescript
51
- import { setConfigValue, readConfigFile } from "@powerduck/conf-patch";
50
+ import { setConfigValue } from "@powerduck/conf-patch";
52
51
 
53
52
  // Atomic write with file locking
54
53
  await setConfigValue("config.yaml", ["database", "port"], 5432);
55
-
56
- const content = await readConfigFile("config.yaml");
57
- console.log(content);
58
54
  ```
59
55
 
60
- ### Validate an OpenAPI document
56
+ ---
61
57
 
62
- ```typescript
63
- import { validateOpenAPISpec, OpenApiValidationError } from "@powerduck/conf-patch";
58
+ ## Links
64
59
 
65
- try {
66
- const doc = await validateOpenAPISpec(`
67
- openapi: 3.1.0
68
- info:
69
- title: Demo API
70
- version: 1.0.0
71
- paths: {}
72
- `);
73
- console.log("Valid. openapi =", doc.openapi);
74
- } catch (error) {
75
- if (error instanceof OpenApiValidationError) {
76
- console.error(`[${error.code}]`, error.message);
77
- }
78
- }
79
- ```
60
+ - [Official Website](https://www.powerduck.com/opensource/conf-patch.html)
61
+ - [Documentation](https://www.powerduck.com/docs/conf-patch/introduction)
62
+ - [Live Demo](https://www.powerduck.com/demo/conf-patch)
63
+ - [GitHub](https://github.com/PowerDuckie/conf-patch)
64
+ - [npm](https://www.npmjs.com/package/@powerduck/conf-patch)
80
65
 
81
66
  ---
82
67
 
@@ -119,178 +104,212 @@ try {
119
104
 
120
105
  Import from `@powerduck/conf-patch/core` for pure string operations with no filesystem access.
121
106
 
122
- ### `patchContent(content, ops, format, options?)`
107
+ ### `patchContent(content, operations, format)`
108
+
109
+ Patches a JSON/JSONC/YAML string with RFC 6902 operations.
123
110
 
124
111
  ```typescript
125
- function patchContent(
126
- content: string,
127
- ops: JsonPatchOp[],
128
- format: ConfigFormat,
129
- options?: PatchContentOptions,
130
- ): string;
112
+ import { patchContent } from "@powerduck/conf-patch/core";
113
+
114
+ const result = patchContent(
115
+ '{"a": 1, "b": 2}',
116
+ [{ op: "replace", path: ["a"], value: 10 }],
117
+ "json",
118
+ );
131
119
  ```
132
120
 
133
- Applies an array of RFC 6902 patch operations. Returns the patched content.
121
+ ### `setContentValue(content, path, value, format)`
134
122
 
135
- | Parameter | Type | Description |
136
- |---|---|---|
137
- | `content` | `string` | The raw configuration content |
138
- | `ops` | `JsonPatchOp[]` | Operations to apply in order |
139
- | `format` | `ConfigFormat` | `"json" \| "jsonc" \| "yaml"` |
140
- | `options.strict` | `boolean` | When `true` (default), failed operations throw. When `false`, skipped with a warning. |
123
+ Convenience function to set a single value at a path.
141
124
 
142
- ### `setContentValue(content, path, value, format)`
125
+ ```typescript
126
+ import { setContentValue } from "@powerduck/conf-patch/core";
143
127
 
144
- Sets or creates a single value. Internally calls `patchContent` with one `add` operation.
128
+ const result = setContentValue(
129
+ '{"server": {"port": 3000}}',
130
+ ["server", "port"],
131
+ 8080,
132
+ "json",
133
+ );
134
+ ```
145
135
 
146
136
  ### `deleteContentValue(content, path, format)`
147
137
 
148
- Removes a key or array element. The path must exist.
138
+ Delete a value at a path.
149
139
 
150
- ---
151
-
152
- ## File Layer (Node.js / Electron)
140
+ ```typescript
141
+ import { deleteContentValue } from "@powerduck/conf-patch/core";
153
142
 
154
- Import from the main entry `@powerduck/conf-patch`.
143
+ const result = deleteContentValue('{"a": 1, "b": 2}', ["b"], "json");
144
+ ```
155
145
 
156
- ### `readConfigFile(filePath)`
146
+ ### `detectFormat(filePath)`
157
147
 
158
- Reads UTF-8 text from a local file path or `file://` URL.
148
+ Detect format from file extension.
159
149
 
160
- ### `writeConfigFile(filePath, content, options?)`
150
+ ```typescript
151
+ import { detectFormat } from "@powerduck/conf-patch";
161
152
 
162
- Writes content using an atomic write (temp file + rename) and exclusive file locking.
153
+ const format = detectFormat("./config.yaml"); // "yaml"
154
+ ```
163
155
 
164
- ### `patchConfigFile(filePath, ops, options?)`
156
+ ---
165
157
 
166
- The primary file-layer transaction: read the file inside a lock, apply `patchContent`, and write back atomically only if the content changed.
158
+ ## File Layer (Node.js / Electron)
167
159
 
168
- ### `setConfigValue(filePath, path, value, options?)`
160
+ Import from `@powerduck/conf-patch` for filesystem operations with atomic writes and file locking.
169
161
 
170
- Adds or replaces a nested value in a file.
162
+ ### `readConfigFile(filePath, options?)`
171
163
 
172
- ### `deleteConfigValue(filePath, path, options?)`
164
+ Read and parse a config file.
173
165
 
174
- Removes an existing nested value from a file.
166
+ ```typescript
167
+ import { readConfigFile } from "@powerduck/conf-patch";
175
168
 
176
- ### `withFileLock(filePath, callback, options?)`
169
+ const config = await readConfigFile("./config.json");
170
+ console.log(config.value);
171
+ ```
177
172
 
178
- Runs an async callback under a process-local queue and an exclusive lock file.
173
+ ### `writeConfigFile(filePath, value, options?)`
179
174
 
180
- ---
175
+ Write a config value to a file with atomic write.
181
176
 
182
- ## OpenAPI Validation
177
+ ```typescript
178
+ import { writeConfigFile } from "@powerduck/conf-patch";
183
179
 
184
- ### `validateOpenAPISpec(input, options?)`
180
+ await writeConfigFile("./config.json", { name: "app", version: "1.0.0" });
181
+ ```
185
182
 
186
- Validates raw OpenAPI/Swagger content (JSON or YAML). Returns the validated document on success, throws `OpenApiValidationError` on failure.
183
+ ### `setConfigValue(filePath, path, value, options?)`
187
184
 
188
- ### `validateOpenAPIFile(filePath, options?)`
185
+ Set a single value in a config file with atomic write and file locking.
189
186
 
190
- Convenience wrapper that validates from a file path. Requires `allowedRootDirectory` for path sandboxing.
187
+ ```typescript
188
+ import { setConfigValue } from "@powerduck/conf-patch";
191
189
 
192
- ### `OpenApiValidationError`
190
+ await setConfigValue("./config.yaml", ["database", "host"], "localhost");
191
+ ```
193
192
 
194
- Structured error with a machine-readable `code` property:
193
+ ### `patchConfigFile(filePath, operations, options?)`
195
194
 
196
- | Code | Meaning |
197
- |---|---|
198
- | `INVALID_OPTION` | Invalid option value provided |
199
- | `INPUT_TOO_LARGE` | Raw content exceeds `maxInputBytes` |
200
- | `FILE_INPUT_FORBIDDEN` | File input used without `allowedRootDirectory` |
201
- | `PATH_OUTSIDE_ROOT` | File path resolves outside `allowedRootDirectory` |
202
- | `PARSE_ERROR` | Failed to parse JSON/YAML content |
203
- | `UNSUPPORTED_VERSION` | Document does not declare a supported OpenAPI/Swagger version |
204
- | `SPEC_VALIDATION_FAILED` | The spec failed OpenAPI validation |
205
- | `OPERATION_TIMEOUT` | Operation exceeded `timeoutMs` |
206
- | `OPERATION_ABORTED` | Operation was aborted via `signal` |
195
+ Apply RFC 6902 patch operations to a config file.
207
196
 
208
- ---
197
+ ```typescript
198
+ import { patchConfigFile } from "@powerduck/conf-patch";
209
199
 
210
- ## Types
200
+ await patchConfigFile("./config.json", [
201
+ { op: "replace", path: ["version"], value: "2.0.0" },
202
+ { op: "add", path: ["author"], value: "Powerduck" },
203
+ ]);
204
+ ```
211
205
 
212
- ```typescript
213
- type ConfigFormat = "json" | "jsonc" | "yaml";
206
+ ### `withFileLock(filePath, callback, options?)`
214
207
 
215
- interface JsonPatchOp {
216
- op: "add" | "replace" | "remove";
217
- path: (string | number)[];
218
- value?: unknown; // Required for "add" and "replace"
219
- }
208
+ Acquire a file lock and execute a callback.
220
209
 
221
- interface PatchContentOptions {
222
- strict?: boolean; // default: true
223
- }
210
+ ```typescript
211
+ import { withFileLock } from "@powerduck/conf-patch";
224
212
 
225
- interface PatchConfigOptions {
226
- format?: ConfigFormat;
227
- strict?: boolean;
228
- lock?: boolean;
229
- lockTimeoutMs?: number;
230
- lockRetryDelayMs?: number;
231
- lockStaleThresholdMs?: number;
232
- allowStaleRecovery?: boolean;
233
- }
213
+ await withFileLock("./config.json", async () => {
214
+ // Critical section - no other process can modify the file
215
+ await setConfigValue("./config.json", ["counter"], 42);
216
+ });
234
217
  ```
235
218
 
236
219
  ---
237
220
 
238
- ## Electron IPC Example
221
+ ## OpenAPI Validation
239
222
 
240
- ```javascript
241
- // Electron main process (CommonJS)
242
- const { setConfigValue, readConfigFile } = require("@powerduck/conf-patch");
223
+ Built-in OpenAPI spec validation with secure input handling.
243
224
 
244
- ipcMain.handle("config:set", async (_event, { filePath, path, value }) => {
245
- try {
246
- await setConfigValue(filePath, path, value);
247
- return { success: true };
248
- } catch (error) {
249
- return { success: false, error: error.message };
250
- }
251
- });
225
+ ### `validateOpenAPISpec(input, options?)`
226
+
227
+ Validate a raw YAML or JSON OpenAPI document.
252
228
 
253
- ipcMain.handle("config:read", async (_event, { filePath }) => {
254
- try {
255
- const data = await readConfigFile(filePath);
256
- return { success: true, data };
257
- } catch (error) {
258
- return { success: false, error: error.message };
229
+ ```typescript
230
+ import {
231
+ validateOpenAPISpec,
232
+ OpenApiValidationError,
233
+ } from "@powerduck/conf-patch";
234
+
235
+ try {
236
+ const doc = await validateOpenAPISpec(`
237
+ openapi: 3.1.0
238
+ info:
239
+ title: Demo API
240
+ version: 1.0.0
241
+ paths: {}
242
+ `);
243
+ console.log("Valid. openapi =", doc.openapi);
244
+ } catch (error) {
245
+ if (error instanceof OpenApiValidationError) {
246
+ console.error(`[${error.code}]`, error.message);
259
247
  }
260
- });
248
+ }
261
249
  ```
262
250
 
263
- ---
251
+ ### `validateOpenAPIFile(filePath, options?)`
264
252
 
265
- ## Development
253
+ Validate an OpenAPI document from a local file.
266
254
 
267
- ```bash
268
- # Install dependencies
269
- npm install
255
+ ```typescript
256
+ import { validateOpenAPIFile } from "@powerduck/conf-patch";
270
257
 
271
- # Type check
272
- npm run typecheck
258
+ const doc = await validateOpenAPIFile("./openapi.json", {
259
+ allowedRootDirectory: "./specs",
260
+ maxInputBytes: 5 * 1024 * 1024,
261
+ });
262
+ ```
273
263
 
274
- # Build (ESM + CJS + type declarations)
275
- npm run build
264
+ ### Validation Options
276
265
 
277
- # Run tests
278
- npm test
266
+ | Option | Type | Default | Description |
267
+ | ---------------------- | --------------------- | ----------- | ----------------------------------------------------------- |
268
+ | `inputKind` | `"content" \| "file"` | `"content"` | Whether input is raw content or a file path |
269
+ | `baseFilePath` | `string` | - | Base file path for reference resolution |
270
+ | `allowedRootDirectory` | `string` | - | Root directory for file operations (required for file mode) |
271
+ | `timeoutMs` | `number` | `15000` | Operation timeout in milliseconds |
272
+ | `maxInputBytes` | `number` | `5242880` | Maximum input size in bytes |
273
+ | `maxDocumentNodes` | `number` | `100000` | Maximum nodes in parsed document |
274
+ | `maxDocumentDepth` | `number` | `100` | Maximum nesting depth |
279
275
 
280
- # Watch mode
281
- npx vitest watch
282
- ```
276
+ ---
277
+
278
+ ## Error Codes
279
+
280
+ | Code | Description |
281
+ | ------------------------ | ----------------------------------------------- |
282
+ | `INVALID_OPTION` | Invalid option value provided |
283
+ | `FILE_INPUT_FORBIDDEN` | File input requires `allowedRootDirectory` |
284
+ | `PATH_OUTSIDE_ROOT` | File path is outside the allowed root directory |
285
+ | `INPUT_TOO_LARGE` | Input exceeds maximum allowed size |
286
+ | `INPUT_FILE_TOO_LARGE` | File exceeds maximum allowed size |
287
+ | `PARSE_ERROR` | Failed to parse the document |
288
+ | `INVALID_DOCUMENT_SHAPE` | Document is not a valid JSON object |
289
+ | `UNSUPPORTED_VERSION` | Unsupported OpenAPI/Swagger version |
290
+ | `DOCUMENT_TOO_LARGE` | Document exceeds maximum node count |
291
+ | `DOCUMENT_TOO_DEEP` | Document exceeds maximum nesting depth |
292
+ | `SPEC_VALIDATION_FAILED` | OpenAPI validation failed |
293
+ | `OPERATION_TIMEOUT` | Operation timed out |
294
+ | `OPERATION_ABORTED` | Operation was aborted |
295
+ | `UNKNOWN_ERROR` | Unknown error occurred |
283
296
 
284
297
  ---
285
298
 
286
- ## Related Packages
299
+ ## TypeScript Types
287
300
 
288
- - [`@powerduck/openapi-parser`](https://www.npmjs.com/package/@powerduck/openapi-parser) — OpenAPI 3.2 parser, validator, and upgrader
289
- - [`@powerduck/openapi-request`](https://www.npmjs.com/package/@powerduck/openapi-request) — Execute OpenAPI operations with full parameter serialization
290
- - [`@powerduck/openapi-codegen`](https://www.npmjs.com/package/@powerduck/openapi-codegen) — Generate runnable request examples in 21 languages
301
+ ```typescript
302
+ import type {
303
+ PatchOperation,
304
+ ConfigFormat,
305
+ ValidateOpenApiOptions,
306
+ OpenApiValidationError,
307
+ AnyOpenAPIDocument,
308
+ } from "@powerduck/conf-patch";
309
+ ```
291
310
 
292
311
  ---
293
312
 
294
313
  ## License
295
314
 
296
- MIT
315
+ MIT © [POWERDUCK LIMITED](https://www.powerduck.com)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powerduck/conf-patch",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
4
4
  "description": "Two-layer configuration editor: pure core for patching JSON/JSONC/YAML strings (browser-safe), plus file layer with atomic writes and locking for Node.js/Electron. RFC 6902 JSON Patch, comment and formatting preservation, OpenAPI validation.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",