jfather 0.1.0 → 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 CHANGED
@@ -1,6 +1,6 @@
1
1
  MIT License
2
2
 
3
- Copyright (c) 2020-2024 Sébastien Règne
3
+ Copyright (c) 2024 Sébastien Règne
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy
6
6
  of this software and associated documentation files (the "Software"), to deal
package/README.md CHANGED
@@ -10,100 +10,324 @@
10
10
  [![coverage][img-coverage]][link-coverage]
11
11
  [![semver][img-semver]][link-semver]
12
12
 
13
- > Boys use JSON; Men use JFather.
13
+ > _Boys use JSON; Men use JFather._
14
14
 
15
15
  ## Overview
16
16
 
17
- **JFather** is
18
- [JSON](https://www.json.org/json-fr.html "JavaScript Object Notation") with
19
- features to merge, extend and override JSON objects.
17
+ JFather is a utility library to **merge**, **extend** and **override**
18
+ [JSON](https://www.json.org/json-fr.html "JavaScript Object Notation") objects.
20
19
 
21
20
  ```JavaScript
22
21
  import JFather from "jfather";
23
22
 
23
+ // Merge two objects.
24
24
  const merged = JFather.merge(
25
- { foo: "a", bar: "alpha" },
26
- { foo: "b", baz: "beta" },
25
+ { "foo": "a", "bar": "alpha" },
26
+ { "foo": "b", "baz": "beta" }
27
27
  );
28
-
29
28
  console.log(merged);
30
- // { foo: "b", bar: "alpha", baz: "beta" }
31
- ```
32
-
33
- ## Install
29
+ // { "foo": "b", "bar": "alpha", "baz": "beta" }
34
30
 
35
- ### Node.js
31
+ // Extend an object.
32
+ const extended = await JFather.extend({
33
+ "$extends": "https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json#members[1]",
34
+ "age": 34,
35
+ "quote": "With great fist comes great KO"
36
+ });
37
+ console.log(extended);
38
+ // {
39
+ // "name": "Madame Uppercut",
40
+ // "age": 34,
41
+ // "secretIdentity": "Jane Wilson",
42
+ // "powers": [
43
+ // "Million tonne punch",
44
+ // "Damage resistance",
45
+ // "Superhuman reflexes"
46
+ // ],
47
+ // "quote": "With great fist comes great KO"
48
+ // }
36
49
 
37
- JFather is published on [npm][link-npm].
50
+ // Override an object.
51
+ const overridden = await JFather.merge(
52
+ { "foo": ["a", "alpha"] },
53
+ { "$foo[0]": "A", "$foo[]": ["BETA"] }
54
+ );
55
+ console.log(overridden);
56
+ // {
57
+ // "foo": ["A", "alpha", "BETA"]
58
+ // }
38
59
 
39
- ```JavaScript
40
- import JFather from "jfather";
60
+ // Extend, merge and override an object.
61
+ const allIn = await JFather.extend({
62
+ "$extends": "https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json#members[0]",
63
+ "age": 27,
64
+ "$powers[2]": "Atomic breath",
65
+ "$powers[]": ["Matter Creation", "Reality Warping"],
66
+ "quote": "I'm no God. I'm not even a man. I'm just Molecule Man."
67
+ });
68
+ console.log(allIn);
69
+ // {
70
+ // "name": "Molecule Man",
71
+ // "age": 27,
72
+ // "secretIdentity": "Dan Jukes",
73
+ // "powers": [
74
+ // "Radiation resistance",
75
+ // "Turning tiny",
76
+ // "Atomic breath",
77
+ // "Matter Creation",
78
+ // "Reality Warping
79
+ // ],
80
+ // "quote": "I'm no God. I'm not even a man. I'm just Molecule Man."
81
+ // }
41
82
  ```
42
83
 
43
- ### Deno
44
-
45
- The library is available in [Deno](https://deno.land/x/jfather).
84
+ ## Installation
46
85
 
47
- ```JavaScript
48
- import JFather from "https://deno.land/x/jfather/mod.js";
49
- ```
50
-
51
- ### Browsers
52
-
53
- It can also be accessed directly from the CDN
54
- [esm.sh](https://esm.sh/jfather) (ou
86
+ JFather is published on [npm][link-npm] (its CDN:
87
+ [esm.sh](https://esm.sh/jfather),
55
88
  [jsDelivr](https://www.jsdelivr.com/package/npm/jfather),
56
- [UNPKG](https://unpkg.com/browse/jfather/)) :
89
+ [UNPKG](https://unpkg.com/browse/jfather/)) and
90
+ [Deno](https://deno.land/x/jfather).
57
91
 
58
92
  ```JavaScript
93
+ // Node.js and Bun (after `npm install jfather`):
94
+ import JFather from "jfather";
95
+
96
+ // Browsers:
59
97
  import JFather from "https://esm.sh/jfather@0";
60
- // import JFather from "https://cdn.jsdelivr.net/npm/jfather@0";
61
- // import JFather from "https://unpkg.com/jfather@0";
98
+ import JFather from "https://cdn.jsdelivr.net/npm/jfather@0";
99
+ import JFather from "https://unpkg.com/jfather@0";
100
+
101
+ // Deno:
102
+ import JFather from "https://deno.land/x/jfather/mod.js";
62
103
  ```
63
104
 
64
- ## API
105
+ ## Features
65
106
 
66
- - [`merge()`](#merge)
67
- - [`load()`](#load)
68
- - [`parse()`](#parse)
107
+ ### Merge
69
108
 
70
- ### merge()
109
+ <!-- markdownlint-disable no-inline-html -->
110
+ <table>
111
+ <tr>
112
+ <th><code>parent</code></th>
113
+ <th><code>child</code></th>
114
+ <th><code>JFather.merge(parent, child)</code></th>
115
+ </tr>
116
+ <tr>
117
+ <td><pre lang="JSON"><code>1</code></pre></td>
118
+ <td><pre lang="JSON"><code>2</code></pre></td>
119
+ <td><pre lang="JSON"><code>2</code></pre></td>
120
+ </tr>
121
+ <tr>
122
+ <td><pre lang="JSON"><code>{
123
+ "foo": "alpha",
124
+ "bar": "ALPHA"
125
+ }</code></pre></td>
126
+ <td><pre lang="JSON"><code>{
127
+ "foo": "beta",
128
+ "baz": "BETA"
129
+ }</code></pre></td>
130
+ <td><pre lang="JSON"><code>{
131
+ "foo": "beta",
132
+ "bar": "ALPHA",
133
+ "baz": "BETA"
134
+ }</code></pre></td>
135
+ </tr>
136
+ <tr>
137
+ <td><pre lang="JSON"><code>{
138
+ "foo": [1, 10, 11]
139
+ }</code></pre></td>
140
+ <td><pre lang="JSON"><code>{
141
+ "foo": [2, 20, 22]
142
+ }</code></pre></td>
143
+ <td><pre lang="JSON"><code>{
144
+ "foo": [2, 20, 22]
145
+ }</code></pre></td>
146
+ </tr>
147
+ </table>
148
+ <!-- markdownlint-enable no-inline-html -->
71
149
 
72
- Merge two variables.
150
+ ### Extend
73
151
 
74
152
  <!-- markdownlint-disable no-inline-html -->
75
153
  <table>
76
154
  <tr>
77
- <th><code>a</code></th>
78
- <th><code>b</code></th>
79
- <th><code>JFather.merge(a, b)</code></th>
155
+ <th><code>https://foo.bar/parent.json</code></th>
156
+ <th><code>child</code></th>
157
+ <th><code>await JFather.extend(child)</code></th>
80
158
  </tr>
81
159
  <tr>
82
- <td><code>1</code></td>
83
- <td><code>2</code></td>
84
- <td><code>2</code></td>
160
+ <td><pre lang="JSON"><code>{
161
+ "baz": "qux"
162
+ }</code></pre></td>
163
+ <td><pre lang="JSON"><code>{
164
+ "$extends": "https://foo.bar/parent.json"
165
+ }</code></pre></td>
166
+ <td><pre lang="JSON"><code>{
167
+ "baz": "qux"
168
+ }</code></pre></td>
85
169
  </tr>
86
170
  <tr>
87
- <td><code>{ foo: "alpha", bar: "ALPHA" }</code></td>
88
- <td><code>{ foo: "beta", baz: "BETA" }</code></td>
89
- <td><code>{ foo: "beta", bar: "ALPHA", baz: "BETA" }</code></td>
171
+ <td><pre lang="JSON"><code>{
172
+ "baz": "qux"
173
+ }</code></pre></td>
174
+ <td><pre lang="JSON"><code>{
175
+ "$extends": "https://foo.bar/parent.json",
176
+ "baz": "quux"
177
+ }</code></pre></td>
178
+ <td><pre lang="JSON"><code>{
179
+ "baz": "quux"
180
+ }</code></pre></td>
90
181
  </tr>
182
+ <tr>
183
+ <td><pre lang="JSON"><code>{
184
+ "baz": "qux"
185
+ }</code></pre></td>
186
+ <td><pre lang="JSON"><code>{
187
+ "$extends": "https://foo.bar/parent.json",
188
+ "quux": "corge"
189
+ }</code></pre></td>
190
+ <td><pre lang="JSON"><code>{
191
+ "baz": "qux",
192
+ "quux": "corge"
193
+ }</code></pre></td>
91
194
  </tr>
92
195
  <tr>
93
- <td><code>{ foo: [1, 10, 11] }</code></td>
94
- <td><code>{ foo: [2, 20, 22] }</code></td>
95
- <td><code>{ foo: [2, 20, 22] }</code></td>
196
+ <td><pre lang="JSON"><code>{
197
+ "baz": "qux"
198
+ }</code></pre></td>
199
+ <td><pre lang="JSON"><code>{
200
+ "quux": {
201
+ "$extends": "https://foo.bar/parent.json",
202
+ "corge": "grault"
203
+ }
204
+ }</code></pre></td>
205
+ <td><pre lang="JSON"><code>{
206
+ "quux": {
207
+ "baz": "qux",
208
+ "corge": "grault"
209
+ }
210
+ }</code></pre></td>
211
+ </tr>
212
+ <tr>
213
+ <td><pre lang="JSON"><code>{
214
+ "baz": {
215
+ "qux": [1, 2],
216
+ "quux": "a"
217
+ },
218
+ "corge": true
219
+ }</code></pre></td>
220
+ <td><pre lang="JSON"><code>{
221
+ "$extends": "https://foo.bar/parent.json#baz"
222
+ }</code></pre></td>
223
+ <td><pre lang="JSON"><code>{
224
+ "qux": [1, 2],
225
+ "quux": "a"
226
+ }</code></pre></td>
96
227
  </tr>
97
228
  </table>
98
229
  <!-- markdownlint-enable no-inline-html -->
99
230
 
100
- ### load()
231
+ ### Override
101
232
 
102
- Load an object from an URL.
233
+ <!-- markdownlint-disable no-inline-html -->
234
+ <table>
235
+ <tr>
236
+ <th><code>parent</code></th>
237
+ <th><code>child</code></th>
238
+ <th><code>JFather.merge(parent, child)</code></th>
239
+ </tr>
240
+ <tr>
241
+ <td><pre lang="JSON"><code>{
242
+ "foo": ["a", "Alpha"]
243
+ }</code></pre></td>
244
+ <td><pre lang="JSON"><code>{
245
+ "$foo[]": ["b", "Beta"]
246
+ }</code></pre></td>
247
+ <td><pre lang="JSON"><code>{
248
+ "foo": ["a", "Alpha", "b", "Beta"]
249
+ }</code></pre></td>
250
+ </tr>
251
+ <tr>
252
+ <td><pre lang="JSON"><code>{
253
+ "foo": ["a", "Alpha"]
254
+ }</code></pre></td>
255
+ <td><pre lang="JSON"><code>{
256
+ "$foo[0]": "A"
257
+ }</code></pre></td>
258
+ <td><pre lang="JSON"><code>{
259
+ "foo": ["A", "Alpha"]
260
+ }</code></pre></td>
261
+ </tr>
262
+ <tr>
263
+ <td><pre lang="JSON"><code>{
264
+ "foo": [{ "bar": ["a"] }]
265
+ }</code></pre></td>
266
+ <td><pre lang="JSON"><code>{
267
+ "$foo[0]": { "$bar[]": ["b", "c"] }
268
+ }</code></pre></td>
269
+ <td><pre lang="JSON"><code>{
270
+ "foo": [{ "bar": ["a", "b", "c"] }]
271
+ }</code></pre></td>
272
+ </tr>
273
+ </table>
274
+ <!-- markdownlint-enable no-inline-html -->
103
275
 
104
- ### parse()
276
+ ## API
277
+
278
+ - [`merge()`](#merge)
279
+ - [`extend()`](#extend)
280
+ - [`load()`](#load)
281
+ - [`parse()`](#parse)
282
+
283
+ ### `merge()`
284
+
285
+ Merge and override `parent` with `child`.
286
+
287
+ ```JavaScript
288
+ JFather.merge(parent, child);
289
+ ```
290
+
291
+ - Parameters:
292
+ - `parent`: The parent object.
293
+ - `child`: The child object.
294
+ - Returns: The merged object.
295
+
296
+ ### `extend()`
297
+
298
+ Extend `obj`, merge and override.
299
+
300
+ ```JavaScript
301
+ JFather.extend(obj);
302
+ ```
303
+
304
+ - Parameter:
305
+ - `obj`: The object with any `$extends` properties.
306
+ - Returns: A promise with the extended object.
307
+
308
+ ### `load()`
309
+
310
+ Load from a `url`, extend, merge and override.
311
+
312
+ ```JavaScript
313
+ JFather.load(url);
314
+ ```
315
+
316
+ - Parameter:
317
+ - `url`: The string containing the URL of a JSON file.
318
+ - Returns: A promise with the loaded object.
319
+
320
+ ### `parse()`
321
+
322
+ Parse a `text`, extend, merge and override.
323
+
324
+ ```JavaScript
325
+ JFather.parse(text);
326
+ ```
105
327
 
106
- Parse a string.
328
+ - Parameter:
329
+ - `text`: The string containing a JSON object.
330
+ - Returns: A promise with the parsed object.
107
331
 
108
332
  [img-npm]: https://img.shields.io/npm/dm/jfather?label=npm&logo=npm&logoColor=whitesmoke
109
333
  [img-build]: https://img.shields.io/github/actions/workflow/status/regseb/jfather/ci.yml?branch=main&logo=github&logoColor=whitesmoke
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jfather",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "JSON with merge, extend and override.",
5
5
  "keywords": [
6
6
  "jfather",
@@ -47,18 +47,18 @@
47
47
  },
48
48
  "devDependencies": {
49
49
  "@prantlf/jsonlint": "14.0.3",
50
- "@prettier/plugin-xml": "3.3.0",
51
- "@stryker-mutator/core": "8.2.3",
52
- "@stryker-mutator/mocha-runner": "8.2.3",
50
+ "@prettier/plugin-xml": "3.3.1",
51
+ "@stryker-mutator/core": "8.2.6",
52
+ "@stryker-mutator/mocha-runner": "8.2.6",
53
53
  "@types/mocha": "10.0.6",
54
- "@types/node": "20.11.17",
54
+ "@types/node": "20.11.24",
55
55
  "@types/sinon": "17.0.3",
56
- "eslint": "8.56.0",
56
+ "eslint": "8.57.0",
57
57
  "eslint-plugin-array-func": "4.0.0",
58
58
  "eslint-plugin-eslint-comments": "3.2.0",
59
59
  "eslint-plugin-import": "2.29.1",
60
- "eslint-plugin-jsdoc": "48.0.6",
61
- "eslint-plugin-mocha": "10.2.0",
60
+ "eslint-plugin-jsdoc": "48.2.0",
61
+ "eslint-plugin-mocha": "10.3.0",
62
62
  "eslint-plugin-n": "16.6.2",
63
63
  "eslint-plugin-no-unsanitized": "4.0.2",
64
64
  "eslint-plugin-promise": "6.1.1",
@@ -70,7 +70,7 @@
70
70
  "npm-package-json-lint": "7.1.0",
71
71
  "prettier": "3.2.5",
72
72
  "sinon": "17.0.1",
73
- "typedoc": "0.25.8",
73
+ "typedoc": "0.25.10",
74
74
  "typescript": "5.3.3",
75
75
  "yaml-lint": "1.7.0"
76
76
  },
package/src/index.js CHANGED
@@ -4,6 +4,6 @@
4
4
  * @author Sébastien Règne
5
5
  */
6
6
 
7
- import { load, merge, parse } from "./jfather.js";
7
+ import { extend, load, merge, parse } from "./jfather.js";
8
8
 
9
- export default { load, merge, parse };
9
+ export default { extend, load, merge, parse };
package/src/jfather.js CHANGED
@@ -38,7 +38,7 @@ export const walk = function (obj, fn) {
38
38
  */
39
39
  export const walkAsync = async function (obj, fn) {
40
40
  if (Object === obj?.constructor) {
41
- return fn(
41
+ return await fn(
42
42
  Object.fromEntries(
43
43
  await Promise.all(
44
44
  Object.entries(obj).map(async ([key, value]) => [
@@ -51,7 +51,7 @@ export const walkAsync = async function (obj, fn) {
51
51
  }
52
52
 
53
53
  if (Array.isArray(obj)) {
54
- return Promise.all(obj.map((v) => walkAsync(v, fn)));
54
+ return await Promise.all(obj.map((v) => walkAsync(v, fn)));
55
55
  }
56
56
 
57
57
  return obj;
@@ -76,8 +76,16 @@ export const clone = function (obj) {
76
76
  * @throws {TypeError} Si le chemin est invalide.
77
77
  */
78
78
  export const query = function (obj, chain) {
79
+ if ("" === chain) {
80
+ return obj;
81
+ }
82
+
79
83
  const re = /^\.(?<prop>\w+)|^\[(?<index>\d+)\]/u;
80
- const sub = { obj, chain };
84
+ const sub = {
85
+ obj,
86
+ // Préfixer le chemin avec un point si nécessaire.
87
+ chain: /^[.[]/u.test(chain) ? chain : "." + chain,
88
+ };
81
89
  while (0 !== sub.chain.length) {
82
90
  const result = re.exec(sub.chain);
83
91
  if (undefined !== result?.groups?.prop) {
@@ -105,7 +113,7 @@ export const merge = function (parent, child) {
105
113
  return clone(child);
106
114
  }
107
115
 
108
- const overrode = /** @type {Record<string, any>} */ ({});
116
+ const overridden = /** @type {Record<string, any>} */ ({});
109
117
  for (const key of new Set([
110
118
  ...Object.keys(parent),
111
119
  ...Object.keys(child),
@@ -118,18 +126,18 @@ export const merge = function (parent, child) {
118
126
  // Si la propriété est dans les deux objets : fusionner les deux
119
127
  // valeurs.
120
128
  if (key in parent && key in child) {
121
- overrode[key] = merge(parent[key], child[key]);
129
+ overridden[key] = merge(parent[key], child[key]);
122
130
  // Si la propriété est seulement dans l'objet parent.
123
131
  } else if (key in parent) {
124
- overrode[key] = clone(parent[key]);
132
+ overridden[key] = clone(parent[key]);
125
133
  // Si la propriété est seulement dans l'objet enfant.
126
134
  } else {
127
- overrode[key] = clone(child[key]);
135
+ overridden[key] = clone(child[key]);
128
136
  }
129
137
 
130
138
  // Si la valeur est un tableau : chercher si l'objet enfant a des
131
139
  // surcharges d'éléments.
132
- if (Array.isArray(overrode[key])) {
140
+ if (Array.isArray(overridden[key])) {
133
141
  const overelemRegex = new RegExp(
134
142
  `^\\$${key}\\[(?<index>\\d*)\\]$`,
135
143
  "u",
@@ -139,28 +147,28 @@ export const merge = function (parent, child) {
139
147
  .filter(([i]) => undefined !== i);
140
148
  for (const [index, value] of overelems) {
141
149
  if ("" === index) {
142
- overrode[key].push(clone(value));
150
+ overridden[key].push(...clone(value));
143
151
  } else {
144
- overrode[key][Number(index)] = merge(
145
- overrode[key][Number(index)],
152
+ overridden[key][Number(index)] = merge(
153
+ overridden[key][Number(index)],
146
154
  value,
147
155
  );
148
156
  }
149
157
  }
150
158
  }
151
159
  }
152
- return overrode;
160
+ return overridden;
153
161
  };
154
162
 
155
163
  /**
156
- * Étendre un objet JSON en utilisant les propriétes <code>"$extends"</code>.
164
+ * Étendre un objet JSON en utilisant les propriétés <code>"$extends"</code>.
157
165
  *
158
166
  * @param {Record<string, any>} obj L'objet qui sera étendu.
159
167
  * @returns {Promise<Record<string, any>>} Une promesse contenant l'objet
160
168
  * étendu.
161
169
  */
162
170
  export const inherit = async function (obj) {
163
- if (undefined === obj?.$extends) {
171
+ if (undefined === obj.$extends) {
164
172
  return obj;
165
173
  }
166
174
 
@@ -174,7 +182,7 @@ export const inherit = async function (obj) {
174
182
  * @param {any} obj L'objet qui sera étendu.
175
183
  * @returns {Promise<any>} Une promesse contenant l'objet étendu.
176
184
  */
177
- export const transform = function (obj) {
185
+ export const extend = function (obj) {
178
186
  return walkAsync(obj, inherit);
179
187
  };
180
188
 
@@ -187,15 +195,16 @@ export const transform = function (obj) {
187
195
  export const load = async function (url) {
188
196
  const response = await fetch(url);
189
197
  const json = await response.json();
190
- return transform(query(json, new URL(url).hash.replace(/^#/u, ".")));
198
+ // Enlever le "#" dans le hash de l'URL.
199
+ return await extend(query(json, new URL(url).hash.slice(1)));
191
200
  };
192
201
 
193
202
  /**
194
203
  * Parse une chaine de caractères.
195
204
  *
196
205
  * @param {string} text La chaine de caractères qui sera parsée.
197
- * @returns {any} L'objet.
206
+ * @returns {Promise<any>} L'objet.
198
207
  */
199
208
  export const parse = function (text) {
200
- return transform(JSON.parse(text));
209
+ return extend(JSON.parse(text));
201
210
  };
package/types/index.d.ts CHANGED
@@ -1,9 +1,11 @@
1
1
  declare namespace _default {
2
+ export { extend };
2
3
  export { load };
3
4
  export { merge };
4
5
  export { parse };
5
6
  }
6
7
  export default _default;
8
+ import { extend } from "./jfather.js";
7
9
  import { load } from "./jfather.js";
8
10
  import { merge } from "./jfather.js";
9
11
  import { parse } from "./jfather.js";
@@ -4,6 +4,6 @@ export function clone(obj: any): any;
4
4
  export function query(obj: Record<string, any>, chain: string): any;
5
5
  export function merge(parent: any, child: any): any;
6
6
  export function inherit(obj: Record<string, any>): Promise<Record<string, any>>;
7
- export function transform(obj: any): Promise<any>;
7
+ export function extend(obj: any): Promise<any>;
8
8
  export function load(url: string): Promise<any>;
9
- export function parse(text: string): any;
9
+ export function parse(text: string): Promise<any>;