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