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 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(((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;
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
- function concatDotPath(prefix, key) {
204
- return (prefix && prefix.length > 0) ? `${prefix}.${key}` : key;
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
- flat({ target, prefix, }) {
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).flatMap(([key, value]) => {
285
- const newKey = concatDotPath(prefix, key);
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
- throw new JSONAccessorError(`Logic error: ${newKey}`);
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).flatMap(([key, value]) => {
329
- const newKey = concatDotPath(prefix, key);
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
- * 유효성 검증 없이 flatten 수행
375
+ * 유효성 검증 없이 평탄화 수행
343
376
  */
344
377
  flatWithoutNavigate({ target, prefix }) {
345
- return Object.entries(target).flatMap(([key, value]) => {
346
- const newKey = concatDotPath(prefix, key);
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
- const typeData = this.explorer?.get(key);
602
- if (value == null) {
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
- const typeData = this.explorer?.get(key);
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;