j-templates 8.0.5 → 8.0.7

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.
@@ -28,7 +28,7 @@ export declare class DiffAsync implements IDiffTreeAsync {
28
28
  * @param value - The new value to compare
29
29
  * @returns Promise that resolves to diff results showing changes
30
30
  */
31
- DiffPath(path: string, value: any): Promise<JsonDiffResult>;
31
+ DiffPath(path: string, value: any, flatten?: boolean): Promise<JsonDiffResult>;
32
32
  /**
33
33
  * Computes diffs for a batch of path/value pairs asynchronously.
34
34
  * @param data - Array of objects with path and value properties
@@ -28,10 +28,10 @@ class DiffAsync {
28
28
  * @param value - The new value to compare
29
29
  * @returns Promise that resolves to diff results showing changes
30
30
  */
31
- async DiffPath(path, value) {
31
+ async DiffPath(path, value, flatten = true) {
32
32
  return await this.workerQueue.Push({
33
33
  method: "diffpath",
34
- arguments: [path, value],
34
+ arguments: [path, value, flatten],
35
35
  });
36
36
  }
37
37
  /**
@@ -21,7 +21,7 @@ export declare class DiffSync implements IDiffTree {
21
21
  * @param value - The new value to compare
22
22
  * @returns Diff results showing changes
23
23
  */
24
- DiffPath(path: string, value: any): import("../../Utils/json").JsonDiffResult;
24
+ DiffPath(path: string, value: any, flatten?: boolean): import("../../Utils/json").JsonDiffResult;
25
25
  /**
26
26
  * Computes diffs for a batch of path/value pairs.
27
27
  * @param data - Array of objects with path and value properties
@@ -25,8 +25,8 @@ class DiffSync {
25
25
  * @param value - The new value to compare
26
26
  * @returns Diff results showing changes
27
27
  */
28
- DiffPath(path, value) {
29
- return this.diffTree.DiffPath(path, value);
28
+ DiffPath(path, value, flatten = true) {
29
+ return this.diffTree.DiffPath(path, value, flatten);
30
30
  }
31
31
  /**
32
32
  * Computes diffs for a batch of path/value pairs.
@@ -27,7 +27,7 @@ export interface IDiffTree {
27
27
  * @param value - The new value to compare
28
28
  * @returns Diff results showing changes
29
29
  */
30
- DiffPath(path: string, value: any): JsonDiffResult;
30
+ DiffPath(path: string, value: any, flatten?: boolean): JsonDiffResult;
31
31
  }
32
32
  /**
33
33
  * Constructor interface for IDiffTree.
@@ -25,7 +25,7 @@ function DiffTreeFactory(jsonDiffFactory, worker) {
25
25
  break;
26
26
  }
27
27
  case "diffpath": {
28
- const diff = diffTree.DiffPath(data.arguments[0], data.arguments[1]);
28
+ const diff = diffTree.DiffPath(data.arguments[0], data.arguments[1], data.arguments[2]);
29
29
  ctx.postMessage(diff);
30
30
  break;
31
31
  }
@@ -133,18 +133,18 @@ function DiffTreeFactory(jsonDiffFactory, worker) {
133
133
  * @param keyFunc - Optional function to extract keys from objects
134
134
  * @returns Diff results showing all changes made
135
135
  */
136
- function UpdateSource(source, path, value, keyFunc) {
136
+ function UpdateSource(source, path, value, flatten, keyFunc) {
137
137
  const diffResult = [];
138
138
  if (keyFunc) {
139
139
  const keyPath = ResolveKeyPath(source, path, keyFunc);
140
140
  if (keyPath !== path) {
141
- const keyDiffResult = UpdateSource(source, keyPath, value, keyFunc);
141
+ const keyDiffResult = UpdateSource(source, keyPath, value, flatten, keyFunc);
142
142
  diffResult.push(...keyDiffResult);
143
143
  }
144
144
  }
145
145
  const sourceValue = GetPathValue(source, path);
146
146
  JsonDiff(value, sourceValue, path, diffResult);
147
- if (keyFunc) {
147
+ if (flatten && keyFunc) {
148
148
  let flattened = {};
149
149
  flattened = FlattenValue(flattened, value, keyFunc);
150
150
  flattened = JsonDeepClone(flattened);
@@ -189,8 +189,8 @@ function DiffTreeFactory(jsonDiffFactory, worker) {
189
189
  * @param value - The new value to compare
190
190
  * @returns Diff results showing changes
191
191
  */
192
- DiffPath(path, value) {
193
- return UpdateSource(this.rootState, path, value, this.keyFunc);
192
+ DiffPath(path, value, flatten = true) {
193
+ return UpdateSource(this.rootState, path, value, flatten, this.keyFunc);
194
194
  }
195
195
  /**
196
196
  * Retrieves the current value at a path.
@@ -6,7 +6,7 @@ import { JsonDiffResult } from "../../Utils/json";
6
6
  * @see StoreSync
7
7
  * @see StoreAsync
8
8
  */
9
- export declare class Store {
9
+ export declare abstract class Store {
10
10
  protected keyFunc?: (value: any) => string | undefined;
11
11
  private rootMap;
12
12
  private createNode;
@@ -31,6 +31,10 @@ export declare class Store {
31
31
  * @returns The value or the default value
32
32
  */
33
33
  Get<O>(id: string, defaultValue: O): O;
34
+ abstract Write(data: unknown, key?: string): Promise<void> | void;
35
+ abstract Patch(key: string, patch: unknown): Promise<void> | void;
36
+ abstract Push(key: string, ...data: unknown[]): Promise<void> | void;
37
+ abstract Splice(key: string, start: number, deleteCount?: number, ...items: unknown[]): Promise<any[]> | any[];
34
38
  /**
35
39
  * Updates the root map with diff results by grouping changes by root path.
36
40
  * @param results - Array of diff results to apply
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.Store = void 0;
4
+ const json_1 = require("../../Utils/json");
4
5
  const observableNode_1 = require("../Tree/observableNode");
5
6
  /**
6
7
  * Base class for observable data store management.
@@ -19,7 +20,7 @@ class Store {
19
20
  this.keyFunc = keyFunc;
20
21
  this.rootMap = new Map();
21
22
  const aliasFunc = keyFunc &&
22
- ((value) => {
23
+ ((value, reactive = true) => {
23
24
  const key = keyFunc(value);
24
25
  if (key === undefined)
25
26
  return undefined;
@@ -27,6 +28,7 @@ class Store {
27
28
  if (rootObject === undefined)
28
29
  throw `No root object found for key: ${key}`;
29
30
  const rootValue = observableNode_1.ObservableNode.Unwrap(rootObject); // rootObject[GET_OBSERVABLE_VALUE];
31
+ reactive && observableNode_1.ObservableNode.Touch(rootValue, key);
30
32
  const alias = rootValue[key];
31
33
  return alias;
32
34
  });
@@ -70,7 +72,7 @@ class Store {
70
72
  if (rootObject === undefined) {
71
73
  if (results.length > 1 || results[0].path.length > 1)
72
74
  throw `Unable to initialize root path ${rootPath} with ${results.length} results and initial path ${results[0].path}`;
73
- const newRootObject = this.createNode({ [rootPath]: results[0].value });
75
+ const newRootObject = this.createNode({ [rootPath]: (0, json_1.JsonDeepClone)(results[0].value) });
74
76
  this.rootMap.set(rootPath, newRootObject);
75
77
  return;
76
78
  }
@@ -47,6 +47,10 @@ export declare class StoreAsync extends Store {
47
47
  * @param keyFunc Optional function to generate a key for a given data value.
48
48
  */
49
49
  constructor(keyFunc?: (value: any) => string | undefined);
50
+ /**
51
+ * Returns a promise that resolve once all current queued writes have completed
52
+ */
53
+ Flush(): Promise<void>;
50
54
  /**
51
55
  * Writes data to the store asynchronously.
52
56
  * This method ensures that write operations are queued and executed in a non-blocking manner.
@@ -80,7 +84,7 @@ export declare class StoreAsync extends Store {
80
84
  * @param items Optional elements to insert into the array.
81
85
  * @returns The array of deleted elements.
82
86
  */
83
- Splice(key: string, start: number, deleteCount?: number, ...items: unknown[]): Promise<any[]>;
87
+ Splice(key: string, start: number, deleteCount?: number, ...items: unknown[]): Promise<any>;
84
88
  /**
85
89
  * Destroys the StoreAsync instance, stopping the async queue and cleaning up the diff instance.
86
90
  * This method should be called when the instance is no longer needed to free up resources.
@@ -4,7 +4,6 @@ exports.StoreAsync = void 0;
4
4
  const asyncQueue_1 = require("../../Utils/asyncQueue");
5
5
  const json_1 = require("../../Utils/json");
6
6
  const diffAsync_1 = require("../Diff/diffAsync");
7
- const observableNode_1 = require("../Tree/observableNode");
8
7
  const store_1 = require("./store");
9
8
  /**
10
9
  * StoreAsync class extends the base Store class to provide asynchronous data management operations.
@@ -52,6 +51,12 @@ class StoreAsync extends store_1.Store {
52
51
  this.queue = new asyncQueue_1.AsyncQueue();
53
52
  this.diff = new diffAsync_1.DiffAsync(keyFunc);
54
53
  }
54
+ /**
55
+ * Returns a promise that resolve once all current queued writes have completed
56
+ */
57
+ async Flush() {
58
+ return this.queue.Flush();
59
+ }
55
60
  /**
56
61
  * Writes data to the store asynchronously.
57
62
  * This method ensures that write operations are queued and executed in a non-blocking manner.
@@ -82,7 +87,7 @@ class StoreAsync extends store_1.Store {
82
87
  throw "Unable to patch undefined value";
83
88
  const json = value.toJSON();
84
89
  const mergedJson = (0, json_1.JsonMerge)(json, patch);
85
- const diffResult = await this.diff.DiffPath(key, mergedJson);
90
+ const diffResult = await this.diff.DiffPath(key, mergedJson, false);
86
91
  this.UpdateRootMap(diffResult);
87
92
  });
88
93
  }
@@ -118,10 +123,9 @@ class StoreAsync extends store_1.Store {
118
123
  async Splice(key, start, deleteCount, ...items) {
119
124
  return await this.queue.Next(async () => {
120
125
  const arr = this.Get(key);
121
- const arrValue = observableNode_1.ObservableNode.Unwrap(arr); // (arr as any)[GET_OBSERVABLE_VALUE] as any[];
122
- const arrCopy = arrValue.slice();
123
- const spliceResult = (0, json_1.JsonDeepClone)(arrCopy.splice(start, deleteCount, ...items));
124
- const diffResult = await this.diff.DiffPath(key, arrCopy);
126
+ const arrValue = arr.toJSON();
127
+ const spliceResult = arrValue.splice(start, deleteCount, ...items);
128
+ const diffResult = await this.diff.DiffPath(key, arrValue);
125
129
  this.UpdateRootMap(diffResult);
126
130
  return spliceResult;
127
131
  });
@@ -67,5 +67,5 @@ export declare class StoreSync extends Store {
67
67
  * @param items Optional elements to insert into the array.
68
68
  * @returns The array of deleted elements.
69
69
  */
70
- Splice(key: string, start: number, deleteCount?: number, ...items: unknown[]): any[];
70
+ Splice(key: string, start: number, deleteCount?: number, ...items: unknown[]): any;
71
71
  }
@@ -68,7 +68,7 @@ class StoreSync extends store_1.Store {
68
68
  throw "Unable to patch undefined value";
69
69
  const json = observableNode_1.ObservableNode.Unwrap(value);
70
70
  const mergedJson = (0, json_1.JsonMerge)(json, patch);
71
- const diffResult = this.diff.DiffPath(key, mergedJson);
71
+ const diffResult = this.diff.DiffPath(key, mergedJson, false);
72
72
  this.UpdateRootMap(diffResult);
73
73
  }
74
74
  /**
@@ -98,10 +98,9 @@ class StoreSync extends store_1.Store {
98
98
  */
99
99
  Splice(key, start, deleteCount, ...items) {
100
100
  const arr = this.Get(key);
101
- const arrValue = observableNode_1.ObservableNode.Unwrap(arr); // (arr as any)[GET_OBSERVABLE_VALUE] as any[];
102
- const arrCopy = arrValue.slice();
103
- const spliceResult = (0, json_1.JsonDeepClone)(arrCopy.splice(start, deleteCount, ...items));
104
- const diffResult = this.diff.DiffPath(key, arrCopy);
101
+ const arrValue = arr.toJSON();
102
+ const spliceResult = arrValue.splice(start, deleteCount, ...items);
103
+ const diffResult = this.diff.DiffPath(key, arrValue);
105
104
  this.UpdateRootMap(diffResult);
106
105
  return spliceResult;
107
106
  }
@@ -46,6 +46,9 @@ export declare namespace ObservableNode {
46
46
  * @param prop Optional property name or index to touch a specific nested property.
47
47
  */
48
48
  function Update<T>(value: T, prop?: keyof T): void;
49
+ function Assign<T>(value: T, prop: keyof T, propValue: any): void;
50
+ function Touch<T>(value: T, prop?: keyof T): void;
51
+ function Read<T>(value: T, prop: keyof T): void;
49
52
  /**
50
53
  * Merges a new value into an observable node in-place, preserving the node's object identity.
51
54
  * Computes the diff between the node's current value and the provided value, then applies only
@@ -79,5 +82,5 @@ export declare namespace ObservableNode {
79
82
  * @param alias Optional function to transform values before making them observable.
80
83
  * @returns A function that creates observable nodes from plain values.
81
84
  */
82
- function CreateFactory(alias?: (value: any) => any | undefined): <T>(value: T) => T;
85
+ function CreateFactory(alias?: (value: any, reactive?: boolean) => any | undefined): <T>(value: T) => T;
83
86
  }
@@ -8,7 +8,8 @@ const NODE_VALUE = Symbol("NODE_VALUE");
8
8
  const NODE_PROXY = Symbol("NODE_PROXY");
9
9
  const toJSON = "toJSON";
10
10
  const IS_NODE = Symbol("IS_NODE");
11
- const OBJECT_SCOPE = Symbol("ARRAY_SCOPE");
11
+ const OBJECT_SCOPE = Symbol("OBJECT_SCOPE");
12
+ const ITERATOR_SCOPE = Symbol("ITERATOR_SCOPE");
12
13
  function Identity(value) {
13
14
  return value;
14
15
  }
@@ -31,15 +32,19 @@ function getOwnPropertyDescriptorArray(target, prop) {
31
32
  };
32
33
  }
33
34
  function has(value, prop) {
35
+ observableScope_1.ObservableScope.Touch(value[ITERATOR_SCOPE]);
34
36
  return Object.hasOwn(value[NODE_VALUE], prop);
35
37
  }
36
38
  function hasArray(value, prop) {
39
+ observableScope_1.ObservableScope.Touch(value[ITERATOR_SCOPE]);
37
40
  return Object.hasOwn(value[NODE_VALUE], prop);
38
41
  }
39
42
  function ownKeys(value) {
43
+ observableScope_1.ObservableScope.Touch(value[ITERATOR_SCOPE]);
40
44
  return Object.keys(value[NODE_VALUE]);
41
45
  }
42
46
  function ownKeysArray(value) {
47
+ observableScope_1.ObservableScope.Touch(value[ITERATOR_SCOPE]);
43
48
  return Object.keys(value[NODE_VALUE]);
44
49
  }
45
50
  function UnwrapProxy(value, type = (0, json_2.JsonType)(value)) {
@@ -85,19 +90,22 @@ function CloneProxy(value, type = (0, json_2.JsonType)(value)) {
85
90
  }
86
91
  return value;
87
92
  }
93
+ function GetPropertyScope(object, prop) {
94
+ return object[prop] ??= observableScope_1.ObservableScope.Basic(Property.bind(null, object[NODE_VALUE], prop));
95
+ }
88
96
  function CreateProxyFactory(alias) {
89
97
  function ToJsonCopy(value) {
90
98
  const type = (0, json_2.JsonType)(value);
91
99
  switch (type) {
92
100
  case "array": {
93
101
  const typedValue = value;
94
- const proxy = CreateProxy(typedValue);
95
- return proxy.map(ToJsonCopy);
102
+ const arrayValue = alias(typedValue, false) ?? typedValue;
103
+ return arrayValue.map(ToJsonCopy);
96
104
  }
97
105
  case "object": {
98
106
  const typedValue = alias(value) ?? value;
99
- const proxy = CreateProxy(typedValue);
100
- const keys = Object.keys(proxy);
107
+ const objectValue = alias(typedValue, false) ?? typedValue;
108
+ const keys = Object.keys(objectValue);
101
109
  const copy = {};
102
110
  for (let x = 0; x < keys.length; x++)
103
111
  copy[keys[x]] = ToJsonCopy(typedValue[keys[x]]);
@@ -108,7 +116,7 @@ function CreateProxyFactory(alias) {
108
116
  }
109
117
  }
110
118
  function ToJsonDefault(value) {
111
- return value;
119
+ return (0, json_1.JsonDeepClone)(value);
112
120
  }
113
121
  const ToJson = alias !== undefined ? ToJsonCopy : ToJsonDefault;
114
122
  const readOnly = alias !== undefined;
@@ -204,7 +212,8 @@ function CreateProxyFactory(alias) {
204
212
  function CreateObjectProxy(value) {
205
213
  const wrapper = {
206
214
  [NODE_VALUE]: value,
207
- [NODE_PROXY]: null
215
+ [NODE_PROXY]: null,
216
+ [ITERATOR_SCOPE]: observableScope_1.ObservableScope.Basic(Identity.bind(null, value))
208
217
  };
209
218
  const proxy = wrapper[NODE_PROXY] = new Proxy(wrapper, {
210
219
  get: ObjectProxyGetter,
@@ -224,6 +233,7 @@ function CreateProxyFactory(alias) {
224
233
  return true;
225
234
  }
226
235
  function ObjectProxyGetter(object, prop) {
236
+ observableScope_1.ObservableScope.Touch(object[OBJECT_SCOPE]);
227
237
  switch (prop) {
228
238
  case IS_NODE:
229
239
  return true;
@@ -239,7 +249,7 @@ function CreateProxyFactory(alias) {
239
249
  }
240
250
  }
241
251
  function GetAccessorValue(object, prop) {
242
- const scope = object[prop] ??= observableScope_1.ObservableScope.Basic(Property.bind(null, object[NODE_VALUE], prop));
252
+ const scope = GetPropertyScope(object, prop);
243
253
  const value = observableScope_1.ObservableScope.Value(scope);
244
254
  return CreateProxyFromValue(value);
245
255
  }
@@ -330,14 +340,51 @@ var ObservableNode;
330
340
  * @param value The observable node to touch.
331
341
  * @param prop Optional property name or index to touch a specific nested property.
332
342
  */
333
- function Update(value, prop = OBJECT_SCOPE) {
343
+ function Update(value, prop) {
334
344
  const wrapper = wrapperCache.get(value);
335
345
  if (wrapper) {
336
- const scope = Array.isArray(wrapper) ? wrapper[OBJECT_SCOPE] : wrapper[prop] ?? wrapper[OBJECT_SCOPE];
346
+ const scope = Array.isArray(wrapper) ? wrapper[OBJECT_SCOPE] : (prop && wrapper[prop]) ?? wrapper[ITERATOR_SCOPE];
337
347
  observableScope_1.ObservableScope.Update(scope);
338
348
  }
339
349
  }
340
350
  ObservableNode.Update = Update;
351
+ function Assign(value, prop, propValue) {
352
+ const propExists = Object.hasOwn(value, prop);
353
+ value[prop] = propValue;
354
+ const wrapper = wrapperCache.get(value);
355
+ if (wrapper) {
356
+ if (Array.isArray(wrapper))
357
+ observableScope_1.ObservableScope.Update(wrapper[OBJECT_SCOPE]);
358
+ else {
359
+ const scope = wrapper[prop];
360
+ if (scope) {
361
+ // A prop was just added to the object, fire the iterator scope
362
+ if (!propExists)
363
+ observableScope_1.ObservableScope.Update(wrapper[ITERATOR_SCOPE]);
364
+ observableScope_1.ObservableScope.Update(scope);
365
+ }
366
+ else
367
+ observableScope_1.ObservableScope.Update(wrapper[ITERATOR_SCOPE]);
368
+ }
369
+ }
370
+ }
371
+ ObservableNode.Assign = Assign;
372
+ function Touch(value, prop) {
373
+ const wrapper = wrapperCache.get(value);
374
+ if (wrapper) {
375
+ const scope = prop && GetPropertyScope(wrapper, prop) || wrapper[OBJECT_SCOPE];
376
+ observableScope_1.ObservableScope.Touch(scope);
377
+ }
378
+ }
379
+ ObservableNode.Touch = Touch;
380
+ function Read(value, prop) {
381
+ const wrapper = wrapperCache.get(value);
382
+ if (wrapper) {
383
+ const scope = Array.isArray(wrapper) ? wrapper[OBJECT_SCOPE] : (prop && wrapper[prop]) ?? wrapper[ITERATOR_SCOPE];
384
+ observableScope_1.ObservableScope.Update(scope);
385
+ }
386
+ }
387
+ ObservableNode.Read = Read;
341
388
  /**
342
389
  * Merges a new value into an observable node in-place, preserving the node's object identity.
343
390
  * Computes the diff between the node's current value and the provided value, then applies only
@@ -372,7 +419,7 @@ var ObservableNode;
372
419
  const root = rootNode[NODE_VALUE];
373
420
  if (diffResult.length === 1 && diffResult[0].path.length === 0) {
374
421
  // Replacing rootNode
375
- const rootPatch = diffResult[0].value;
422
+ const rootPatch = (0, json_1.JsonDeepClone)(diffResult[0].value);
376
423
  const rootType = (0, json_2.JsonType)(root);
377
424
  const rootPatchType = (0, json_2.JsonType)(rootPatch);
378
425
  if (rootType !== rootPatchType)
@@ -418,8 +465,7 @@ var ObservableNode;
418
465
  }
419
466
  }
420
467
  const assignValue = pathTuples[y][1];
421
- assignValue[path[y]] = value;
422
- ObservableNode.Update(assignValue, path[y]);
468
+ ObservableNode.Assign(assignValue, path[y], (0, json_1.JsonDeepClone)(value));
423
469
  }
424
470
  }
425
471
  ObservableNode.ApplyDiff = ApplyDiff;
package/Store/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ export { Store } from "./Store/store";
1
2
  export { StoreSync } from "./Store/storeSync";
2
3
  export { StoreAsync } from "./Store/storeAsync";
3
4
  export { ObservableScope } from "./Tree/observableScope";
package/Store/index.js CHANGED
@@ -1,6 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ObservableNode = exports.ObservableScope = exports.StoreAsync = exports.StoreSync = void 0;
3
+ exports.ObservableNode = exports.ObservableScope = exports.StoreAsync = exports.StoreSync = exports.Store = void 0;
4
+ var store_1 = require("./Store/store");
5
+ Object.defineProperty(exports, "Store", { enumerable: true, get: function () { return store_1.Store; } });
4
6
  var storeSync_1 = require("./Store/storeSync");
5
7
  Object.defineProperty(exports, "StoreSync", { enumerable: true, get: function () { return storeSync_1.StoreSync; } });
6
8
  var storeAsync_1 = require("./Store/storeAsync");
@@ -4,6 +4,7 @@
4
4
  export declare class AsyncQueue {
5
5
  private running;
6
6
  private queue;
7
+ Flush(): Promise<void>;
7
8
  /**
8
9
  * Adds a callback to the queue and returns a promise that resolves when it executes.
9
10
  * @template T - The type the callback promise resolves to
@@ -10,6 +10,13 @@ class AsyncQueue {
10
10
  this.running = false;
11
11
  this.queue = list_1.List.Create();
12
12
  }
13
+ Flush() {
14
+ const ret = new Promise(resolve => {
15
+ list_1.List.Add(this.queue, resolve);
16
+ });
17
+ this.Start();
18
+ return ret;
19
+ }
13
20
  /**
14
21
  * Adds a callback to the queue and returns a promise that resolves when it executes.
15
22
  * @template T - The type the callback promise resolves to
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "j-templates",
3
- "version": "8.0.5",
3
+ "version": "8.0.7",
4
4
  "description": "j-templates",
5
5
  "license": "MIT",
6
6
  "repository": "https://github.com/TypesInCode/jTemplates",