@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.
- package/README.md +166 -147
- 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
|
[](https://www.npmjs.com/package/@powerduck/conf-patch)
|
|
6
4
|
[](https://github.com/PowerDuckie/conf-patch/blob/main/LICENSE)
|
|
5
|
+
[](https://www.npmjs.com/package/@powerduck/conf-patch)
|
|
7
6
|
|
|
8
|
-
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
-
|
|
13
|
-
|
|
14
|
-
-
|
|
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
|
|
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
|
|
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
|
-
|
|
56
|
+
---
|
|
61
57
|
|
|
62
|
-
|
|
63
|
-
import { validateOpenAPISpec, OpenApiValidationError } from "@powerduck/conf-patch";
|
|
58
|
+
## Links
|
|
64
59
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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,
|
|
107
|
+
### `patchContent(content, operations, format)`
|
|
108
|
+
|
|
109
|
+
Patches a JSON/JSONC/YAML string with RFC 6902 operations.
|
|
123
110
|
|
|
124
111
|
```typescript
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
-
|
|
121
|
+
### `setContentValue(content, path, value, format)`
|
|
134
122
|
|
|
135
|
-
|
|
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
|
-
|
|
125
|
+
```typescript
|
|
126
|
+
import { setContentValue } from "@powerduck/conf-patch/core";
|
|
143
127
|
|
|
144
|
-
|
|
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
|
-
|
|
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
|
-
|
|
143
|
+
const result = deleteContentValue('{"a": 1, "b": 2}', ["b"], "json");
|
|
144
|
+
```
|
|
155
145
|
|
|
156
|
-
### `
|
|
146
|
+
### `detectFormat(filePath)`
|
|
157
147
|
|
|
158
|
-
|
|
148
|
+
Detect format from file extension.
|
|
159
149
|
|
|
160
|
-
|
|
150
|
+
```typescript
|
|
151
|
+
import { detectFormat } from "@powerduck/conf-patch";
|
|
161
152
|
|
|
162
|
-
|
|
153
|
+
const format = detectFormat("./config.yaml"); // "yaml"
|
|
154
|
+
```
|
|
163
155
|
|
|
164
|
-
|
|
156
|
+
---
|
|
165
157
|
|
|
166
|
-
|
|
158
|
+
## File Layer (Node.js / Electron)
|
|
167
159
|
|
|
168
|
-
|
|
160
|
+
Import from `@powerduck/conf-patch` for filesystem operations with atomic writes and file locking.
|
|
169
161
|
|
|
170
|
-
|
|
162
|
+
### `readConfigFile(filePath, options?)`
|
|
171
163
|
|
|
172
|
-
|
|
164
|
+
Read and parse a config file.
|
|
173
165
|
|
|
174
|
-
|
|
166
|
+
```typescript
|
|
167
|
+
import { readConfigFile } from "@powerduck/conf-patch";
|
|
175
168
|
|
|
176
|
-
|
|
169
|
+
const config = await readConfigFile("./config.json");
|
|
170
|
+
console.log(config.value);
|
|
171
|
+
```
|
|
177
172
|
|
|
178
|
-
|
|
173
|
+
### `writeConfigFile(filePath, value, options?)`
|
|
179
174
|
|
|
180
|
-
|
|
175
|
+
Write a config value to a file with atomic write.
|
|
181
176
|
|
|
182
|
-
|
|
177
|
+
```typescript
|
|
178
|
+
import { writeConfigFile } from "@powerduck/conf-patch";
|
|
183
179
|
|
|
184
|
-
|
|
180
|
+
await writeConfigFile("./config.json", { name: "app", version: "1.0.0" });
|
|
181
|
+
```
|
|
185
182
|
|
|
186
|
-
|
|
183
|
+
### `setConfigValue(filePath, path, value, options?)`
|
|
187
184
|
|
|
188
|
-
|
|
185
|
+
Set a single value in a config file with atomic write and file locking.
|
|
189
186
|
|
|
190
|
-
|
|
187
|
+
```typescript
|
|
188
|
+
import { setConfigValue } from "@powerduck/conf-patch";
|
|
191
189
|
|
|
192
|
-
|
|
190
|
+
await setConfigValue("./config.yaml", ["database", "host"], "localhost");
|
|
191
|
+
```
|
|
193
192
|
|
|
194
|
-
|
|
193
|
+
### `patchConfigFile(filePath, operations, options?)`
|
|
195
194
|
|
|
196
|
-
|
|
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
|
-
|
|
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
|
-
|
|
213
|
-
type ConfigFormat = "json" | "jsonc" | "yaml";
|
|
206
|
+
### `withFileLock(filePath, callback, options?)`
|
|
214
207
|
|
|
215
|
-
|
|
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
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
210
|
+
```typescript
|
|
211
|
+
import { withFileLock } from "@powerduck/conf-patch";
|
|
224
212
|
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
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
|
-
##
|
|
221
|
+
## OpenAPI Validation
|
|
239
222
|
|
|
240
|
-
|
|
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
|
-
|
|
245
|
-
|
|
246
|
-
|
|
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
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
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
|
-
|
|
253
|
+
Validate an OpenAPI document from a local file.
|
|
266
254
|
|
|
267
|
-
```
|
|
268
|
-
|
|
269
|
-
npm install
|
|
255
|
+
```typescript
|
|
256
|
+
import { validateOpenAPIFile } from "@powerduck/conf-patch";
|
|
270
257
|
|
|
271
|
-
|
|
272
|
-
|
|
258
|
+
const doc = await validateOpenAPIFile("./openapi.json", {
|
|
259
|
+
allowedRootDirectory: "./specs",
|
|
260
|
+
maxInputBytes: 5 * 1024 * 1024,
|
|
261
|
+
});
|
|
262
|
+
```
|
|
273
263
|
|
|
274
|
-
|
|
275
|
-
npm run build
|
|
264
|
+
### Validation Options
|
|
276
265
|
|
|
277
|
-
|
|
278
|
-
|
|
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
|
-
|
|
281
|
-
|
|
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
|
-
##
|
|
299
|
+
## TypeScript Types
|
|
287
300
|
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
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
|
+
"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",
|