ac-storage 0.12.0 → 0.14.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/dist/bundle.cjs +120 -32
- package/dist/bundle.cjs.map +1 -1
- package/dist/bundle.mjs +120 -32
- package/dist/bundle.mjs.map +1 -1
- package/package.json +2 -2
package/dist/bundle.cjs
CHANGED
|
@@ -27,7 +27,7 @@ var fs__namespace = /*#__PURE__*/_interopNamespaceDefault(fs$1);
|
|
|
27
27
|
var fs__namespace$1 = /*#__PURE__*/_interopNamespaceDefault(fs$2);
|
|
28
28
|
var path__namespace = /*#__PURE__*/_interopNamespaceDefault(path);
|
|
29
29
|
|
|
30
|
-
let e$1 = class e extends Error{constructor(e){super(e),this.name="TreeNavigateError";}};const t$1="--tree-leaf";var i$1;let r$1 = class r{#e={};#t=".";#i=false;#r=true;constructor(){}static from(e,t={}){const r=new i$1;return r.#e=e,r.#t=t.delimiter??".",r.#i=t.allowWildcard??false,r.#r=t.allowRecursiveWildcard??true,r}subtree(t){const r=t.split(this.#t),a=this.#a(r,this.#e);if(!a)throw new e$1(`Path '${t}' does not exist`);if(i$1.#l(a.value)||i$1.#s(a.value))throw new e$1(`Path '${t}' is not a subtree`);const l=a.path.reduce((
|
|
30
|
+
let e$1 = class e extends Error{constructor(e){super(e),this.name="TreeNavigateError";}};const t$1="--tree-leaf";var i$1;let r$1 = class r{#e={};#t=".";#i=false;#r=true;constructor(){}static from(e,t={}){const r=new i$1;return r.#e=e,r.#t=t.delimiter??".",r.#i=t.allowWildcard??false,r.#r=t.allowRecursiveWildcard??true,r}subtree(t){const r=t.split(this.#t),a=this.#a(r,this.#e);if(!a)throw new e$1(`Path '${t}' does not exist`);if(i$1.#l(a.value)||i$1.#s(a.value))throw new e$1(`Path '${t}' is not a subtree`);const l=a.path.reduce((e,t)=>e[t],this.#e),s=new i$1;return s.#e=l,s.#t=this.#t,s.#i=this.#i,s}get(e,t){return this.walk(e,t)?.value??null}walk(e,t={}){if(""===e)return t.allowIntermediate?{value:this.#e,path:[]}:null;const r=e.split(this.#t),a=this.#a(r,this.#e);if(!a)return null;const l=a.value;return i$1.#l(l)?a:i$1.#s(l)?{value:l.value,path:a.path}:t.allowIntermediate?a:null}trace(e){const t=e.split(this.#t),r={treePath:[]},a=this.#a(t,this.#e,r);let l,s;if(a){const e=a.value;return i$1.#l(e)?(l=true,s=e):i$1.#s(e)?(l=true,s=e.value):(l=false,s=e),{find:true,isLeaf:l,value:s,nodePath:a?.path??[],tracePath:r.treePath,untracePath:t}}return {find:false,isLeaf:false,value:void 0,nodePath:[],tracePath:r.treePath,untracePath:t}}#a(e,t,r={treePath:[]}){const{treePath:a}=r,l=e.shift();if(null==l)return {value:t,path:[]};if("object"!=typeof t||i$1.#s(t))return e.unshift(l),null;if(a.push(l),l in t){const i=this.#a(e,t[l],r);if(i)return {value:i.value,path:[l,...i.path]}}else if(this.#i){if("*"in t){const i=this.#a(e,t["*"],r);if(i)return {value:i.value,path:["*",...i.path]}}if(this.#r&&"**/*"in t)return {value:t["**/*"],path:["**/*"]}}return null}static#l(e){return null==e||"object"!=typeof e}static#s(e){return 1==e[t$1]}};i$1=r$1;
|
|
31
31
|
|
|
32
32
|
class JSONAccessorError extends Error {
|
|
33
33
|
constructor(message) {
|
|
@@ -200,9 +200,15 @@ function isObject(value) {
|
|
|
200
200
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
-
|
|
204
|
-
|
|
203
|
+
/**
|
|
204
|
+
* 경로 문자열을 연결하여 반환
|
|
205
|
+
*/
|
|
206
|
+
function dotJoin(prefix, key) {
|
|
207
|
+
return (prefix != null && prefix.length > 0) ? `${prefix}.${key}` : key;
|
|
205
208
|
}
|
|
209
|
+
/**
|
|
210
|
+
* 값이 json-accessor 호환 가능 타입인지 확인하고 타입명 반환, 호환 불가능할 경우 null 반환
|
|
211
|
+
*/
|
|
206
212
|
function getJSONTypeName(value) {
|
|
207
213
|
if (value == null)
|
|
208
214
|
return 'null';
|
|
@@ -230,10 +236,17 @@ class Flattener {
|
|
|
230
236
|
this.typeChecker = typeChecker;
|
|
231
237
|
}
|
|
232
238
|
/**
|
|
233
|
-
* key-value
|
|
239
|
+
* key-value 쌍에 대한 경로 유효성 검사후 `Array<[key, value]>` 형태로 반환
|
|
240
|
+
*
|
|
241
|
+
* @param key - dot(.)으로 구분된 경로 문자열
|
|
242
|
+
* @param value - 유효성 검사를 위한 값
|
|
243
|
+
* @return 인자를 [[key, value]] 형태로 반환
|
|
234
244
|
*/
|
|
235
245
|
transform(key, value) {
|
|
246
|
+
// [[key, value]] 를 그대로 반환하는건 flat()과의 호환을 위함
|
|
247
|
+
// 추후 구현에서 한 key에서 여러 결과를 리턴하는 등의 확장 가능성이 있음
|
|
236
248
|
if (key === '') {
|
|
249
|
+
// 루트 경로는 비허용
|
|
237
250
|
throw new JSONAccessorError(`Invalid path: ${key}`);
|
|
238
251
|
}
|
|
239
252
|
if (this.navigate == null) {
|
|
@@ -244,7 +257,7 @@ class Flattener {
|
|
|
244
257
|
if (traceResult.isLeaf) {
|
|
245
258
|
const node = traceResult.value;
|
|
246
259
|
this.typeChecker.check(key, value, node);
|
|
247
|
-
if (node.type === 'struct') {
|
|
260
|
+
if (value != null && node.type === 'struct') {
|
|
248
261
|
// struct 형식인 value에 대한 구조 검사
|
|
249
262
|
this.flatStruct({
|
|
250
263
|
target: value,
|
|
@@ -255,6 +268,8 @@ class Flattener {
|
|
|
255
268
|
return [[key, value]];
|
|
256
269
|
}
|
|
257
270
|
else {
|
|
271
|
+
// 경로는 존재하지만 노드의 중간인 경우
|
|
272
|
+
// value가 object 형태라면 flat()을 통해 유효성 검사를 진행
|
|
258
273
|
if (!isObject(value)) {
|
|
259
274
|
throw new JSONAccessorError(`Field '${key}' is not allowed`);
|
|
260
275
|
}
|
|
@@ -264,34 +279,41 @@ class Flattener {
|
|
|
264
279
|
});
|
|
265
280
|
}
|
|
266
281
|
}
|
|
282
|
+
// 경로상 존재하지 않는 경우
|
|
283
|
+
else if (traceResult.tracePath.length === 0) {
|
|
284
|
+
throw new JSONAccessorError(`Field '${key}' is not allowed`);
|
|
285
|
+
}
|
|
286
|
+
// 중간 경로까지는 존재하는 경우
|
|
267
287
|
else {
|
|
268
|
-
// 경로상 존재하지 않는 경우
|
|
269
|
-
// 닿을 수 있는 마지막 노드의 타입이 struct, any 라면 허용됨
|
|
270
288
|
const { tracePath } = traceResult;
|
|
271
289
|
const reached = tracePath.join(DELIMITER);
|
|
272
290
|
const node = this.navigate.get(reached);
|
|
291
|
+
// 닿을 수 있는 마지막 노드의 타입이 struct, any 라면 허용됨
|
|
273
292
|
if (!node || (node.type !== 'struct' && node.type !== 'any')) {
|
|
274
293
|
throw new JSONAccessorError(`Field '${key}' is not allowed`);
|
|
275
294
|
}
|
|
276
295
|
return [[key, value]];
|
|
277
296
|
}
|
|
278
297
|
}
|
|
279
|
-
|
|
298
|
+
/**
|
|
299
|
+
* 객체를 평탄화하여 `Array<[key, value]>`로 반환
|
|
300
|
+
*
|
|
301
|
+
* 유효성 검증을 포함함
|
|
302
|
+
*/
|
|
303
|
+
flat({ target, prefix }) {
|
|
280
304
|
if (!this.navigate) {
|
|
281
305
|
return this.flatWithoutNavigate({ target, prefix });
|
|
282
306
|
}
|
|
283
307
|
const navigate = this.navigate;
|
|
284
|
-
return Object.entries(target)
|
|
285
|
-
|
|
308
|
+
return Object.entries(target)
|
|
309
|
+
.flatMap(([key, value]) => {
|
|
310
|
+
const newKey = dotJoin(prefix, key);
|
|
286
311
|
const node = navigate.get(newKey, { allowIntermediate: true });
|
|
287
312
|
if (node == null) {
|
|
288
313
|
throw new JSONAccessorError(`Field '${newKey}' is not allowed`);
|
|
289
314
|
}
|
|
290
315
|
else if (isJSONTypeData(node)) {
|
|
291
316
|
this.typeChecker.check(newKey, value, node);
|
|
292
|
-
// @TODO : 배열 타입에 대한 세부 처리 필요
|
|
293
|
-
// 현재는 배열 경로까지만 flat이 이루어지므로
|
|
294
|
-
// get/set 시 배열 전체를 가져오거나 덮어쓰게됨
|
|
295
317
|
if (node.type === 'any') {
|
|
296
318
|
return this.flatWithoutNavigate({
|
|
297
319
|
target: value,
|
|
@@ -305,6 +327,12 @@ class Flattener {
|
|
|
305
327
|
structData: node,
|
|
306
328
|
});
|
|
307
329
|
}
|
|
330
|
+
else if (node.type === 'array') {
|
|
331
|
+
// @TODO : 배열 타입에 대한 세부 처리 필요
|
|
332
|
+
// 현재는 배열 경로까지만 flat이 이루어지므로
|
|
333
|
+
// get/set 시 배열 전체를 가져오거나 덮어쓰게됨
|
|
334
|
+
return [[newKey, value]];
|
|
335
|
+
}
|
|
308
336
|
else {
|
|
309
337
|
return [[newKey, value]];
|
|
310
338
|
}
|
|
@@ -316,7 +344,11 @@ class Flattener {
|
|
|
316
344
|
});
|
|
317
345
|
}
|
|
318
346
|
else {
|
|
319
|
-
|
|
347
|
+
// 가져온 값이 JSONTypeData(탐색한 노드의 끝) 도 아니고
|
|
348
|
+
// 일반 object(중간 노드)도 아닌 다른 타입인 경우
|
|
349
|
+
//
|
|
350
|
+
// TreeNavigate<JSONTypeData> 에서 가져온 결과에선 나올 수 없으므로 데이터 오염이라고 판단
|
|
351
|
+
throw new JSONAccessorError(`Data corrupted: unexpected node type encountered. key: '${newKey}', value: ${value} (${typeof value})`);
|
|
320
352
|
}
|
|
321
353
|
});
|
|
322
354
|
}
|
|
@@ -325,8 +357,9 @@ class Flattener {
|
|
|
325
357
|
*/
|
|
326
358
|
flatStruct({ target, prefix, structData }) {
|
|
327
359
|
// struct는 내부 구조에 대한 구조 검증이 없으므로 유효성 검증 없이 단순 평탄화
|
|
328
|
-
return Object.entries(target)
|
|
329
|
-
|
|
360
|
+
return Object.entries(target)
|
|
361
|
+
.flatMap(([key, value]) => {
|
|
362
|
+
const newKey = dotJoin(prefix, key);
|
|
330
363
|
if (isObject(value)) {
|
|
331
364
|
return this.flatWithoutNavigate({
|
|
332
365
|
target: value,
|
|
@@ -339,11 +372,12 @@ class Flattener {
|
|
|
339
372
|
});
|
|
340
373
|
}
|
|
341
374
|
/**
|
|
342
|
-
* 유효성 검증 없이
|
|
375
|
+
* 유효성 검증 없이 평탄화 수행
|
|
343
376
|
*/
|
|
344
377
|
flatWithoutNavigate({ target, prefix }) {
|
|
345
|
-
return Object.entries(target)
|
|
346
|
-
|
|
378
|
+
return Object.entries(target)
|
|
379
|
+
.flatMap(([key, value]) => {
|
|
380
|
+
const newKey = dotJoin(prefix, key);
|
|
347
381
|
if (isObject(value)) {
|
|
348
382
|
return this.flatWithoutNavigate({
|
|
349
383
|
target: value,
|
|
@@ -382,6 +416,8 @@ class CompatibilityChecker {
|
|
|
382
416
|
}
|
|
383
417
|
}
|
|
384
418
|
isCompatible(target, jsonTypeData) {
|
|
419
|
+
// jsonTypeData 타입이 primitive 인 경우
|
|
420
|
+
// union 타입 검사 시 isCompatible() 가 다시 호출된 경우 발생
|
|
385
421
|
if (typeof jsonTypeData !== 'object') {
|
|
386
422
|
return target === jsonTypeData;
|
|
387
423
|
}
|
|
@@ -420,9 +456,7 @@ class CompatibilityChecker {
|
|
|
420
456
|
}
|
|
421
457
|
}
|
|
422
458
|
isArrayCompatible(array, arrayTypeData) {
|
|
423
|
-
if (!arrayTypeData.strict)
|
|
424
|
-
return true;
|
|
425
|
-
if (!arrayTypeData.element)
|
|
459
|
+
if (!arrayTypeData.strict || !arrayTypeData.element)
|
|
426
460
|
return true;
|
|
427
461
|
for (const ele of array) {
|
|
428
462
|
if (!this.isCompatible(ele, arrayTypeData.element)) {
|
|
@@ -526,6 +560,63 @@ class MockJSONFS {
|
|
|
526
560
|
}
|
|
527
561
|
}
|
|
528
562
|
|
|
563
|
+
class DefaultValueProvider {
|
|
564
|
+
#navigate;
|
|
565
|
+
constructor(navigate = null) {
|
|
566
|
+
this.#navigate = navigate;
|
|
567
|
+
}
|
|
568
|
+
/**
|
|
569
|
+
*
|
|
570
|
+
*/
|
|
571
|
+
get(key, baseValue) {
|
|
572
|
+
if (this.#navigate == null) {
|
|
573
|
+
return baseValue;
|
|
574
|
+
}
|
|
575
|
+
const result = this.#navigate.walk(key, { allowIntermediate: true });
|
|
576
|
+
if (result == null) {
|
|
577
|
+
return undefined;
|
|
578
|
+
}
|
|
579
|
+
else if (isJSONTypeData(result.value)) {
|
|
580
|
+
if (baseValue != null) {
|
|
581
|
+
return baseValue;
|
|
582
|
+
}
|
|
583
|
+
else {
|
|
584
|
+
const defaultValue = result.value.default_value;
|
|
585
|
+
return ((defaultValue != null)
|
|
586
|
+
? defaultValue
|
|
587
|
+
: undefined);
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
else {
|
|
591
|
+
// for (const [key, value] of Object.entries(result.value)) {
|
|
592
|
+
// console.log(key, value);
|
|
593
|
+
// }
|
|
594
|
+
return this.#getDefaultData(result.value, baseValue);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
#getDefaultData(typeData, baseValue) {
|
|
598
|
+
if (t$1 in typeData) {
|
|
599
|
+
if (typeData.value.default_value != null) {
|
|
600
|
+
return typeData.value.default_value;
|
|
601
|
+
}
|
|
602
|
+
return undefined;
|
|
603
|
+
}
|
|
604
|
+
else {
|
|
605
|
+
let result = baseValue;
|
|
606
|
+
for (const [key, value] of Object.entries(typeData)) {
|
|
607
|
+
const defaultValue = this.#getDefaultData(value);
|
|
608
|
+
if (defaultValue !== undefined) {
|
|
609
|
+
result ??= {};
|
|
610
|
+
if (!(key in result)) {
|
|
611
|
+
result[key] = defaultValue;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return result;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
|
|
529
620
|
class JSONAccessor {
|
|
530
621
|
static anyJSONType = JSONType.Any().nullable().value;
|
|
531
622
|
filePath;
|
|
@@ -534,6 +625,7 @@ class JSONAccessor {
|
|
|
534
625
|
jsonFS = new JSONFS();
|
|
535
626
|
#tree = null;
|
|
536
627
|
#flatter;
|
|
628
|
+
#defaultValueProvider;
|
|
537
629
|
#isDropped = false;
|
|
538
630
|
#changed = true;
|
|
539
631
|
constructor(filePath, tree = null) {
|
|
@@ -543,9 +635,11 @@ class JSONAccessor {
|
|
|
543
635
|
this.#tree = tree;
|
|
544
636
|
this.explorer = r$1.from(tree, { delimiter: '.', allowWildcard: true, allowRecursiveWildcard: false });
|
|
545
637
|
this.#flatter = new SchemaFlattener(this.explorer);
|
|
638
|
+
this.#defaultValueProvider = new DefaultValueProvider(this.explorer);
|
|
546
639
|
}
|
|
547
640
|
else {
|
|
548
641
|
this.#flatter = new SchemaFlattener();
|
|
642
|
+
this.#defaultValueProvider = new DefaultValueProvider();
|
|
549
643
|
}
|
|
550
644
|
}
|
|
551
645
|
get tree() {
|
|
@@ -598,11 +692,8 @@ class JSONAccessor {
|
|
|
598
692
|
getOne(key) {
|
|
599
693
|
this.#ensureNotDropped();
|
|
600
694
|
let value = this.#getData(key);
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
if (typeData && typeData.default_value != null) {
|
|
604
|
-
value = typeData.default_value;
|
|
605
|
-
}
|
|
695
|
+
if (value == null || typeof value === 'object') {
|
|
696
|
+
value = this.#defaultValueProvider.get(key, value);
|
|
606
697
|
}
|
|
607
698
|
return value;
|
|
608
699
|
}
|
|
@@ -611,11 +702,8 @@ class JSONAccessor {
|
|
|
611
702
|
const result = {};
|
|
612
703
|
for (const key of keys) {
|
|
613
704
|
let value = this.#getData(key);
|
|
614
|
-
if (value == null) {
|
|
615
|
-
|
|
616
|
-
if (typeData && typeData.default_value != null) {
|
|
617
|
-
value = typeData.default_value;
|
|
618
|
-
}
|
|
705
|
+
if (value == null || typeof value === 'object') {
|
|
706
|
+
value = this.#defaultValueProvider.get(key, value);
|
|
619
707
|
}
|
|
620
708
|
const resolved = resolveNestedRef(result, key, true);
|
|
621
709
|
resolved.parent[resolved.key] = value;
|