@aiao/rxdb-angular 0.0.3 → 0.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.
Files changed (41) hide show
  1. package/eslint.config.mjs +58 -0
  2. package/ng-package.json +7 -0
  3. package/package.json +13 -23
  4. package/project.json +48 -0
  5. package/src/InfiniteScrollingList.ts +123 -0
  6. package/src/devtools/devtools.html +99 -0
  7. package/src/devtools/devtools.interface.ts +3 -0
  8. package/src/devtools/devtools.scss +49 -0
  9. package/src/devtools/devtools.spec.ts +30 -0
  10. package/src/devtools/devtools.ts +207 -0
  11. package/src/devtools/entity-table-item.ts +47 -0
  12. package/src/devtools/entity-table-tools.html +56 -0
  13. package/src/devtools/entity-table-tools.scss +8 -0
  14. package/src/devtools/entity-table-tools.ts +153 -0
  15. package/src/devtools/event-tools.html +15 -0
  16. package/src/devtools/event-tools.scss +18 -0
  17. package/src/devtools/event-tools.ts +45 -0
  18. package/src/devtools/scroll-advanced-calc.service.ts +41 -0
  19. package/src/devtools/settings.html +46 -0
  20. package/src/devtools/settings.scss +19 -0
  21. package/src/devtools/settings.ts +122 -0
  22. package/src/hooks.ts +307 -0
  23. package/src/index.ts +7 -0
  24. package/src/rxdb-change-detector.directive.spec.ts +94 -0
  25. package/src/rxdb-change-detector.directive.ts +35 -0
  26. package/src/rxdb.provider.ts +13 -0
  27. package/src/rxdb.service.spec.ts +31 -0
  28. package/src/rxdb.service.ts +35 -0
  29. package/src/test-setup.ts +14 -0
  30. package/src/use-action.spec.ts +88 -0
  31. package/src/use-action.ts +20 -0
  32. package/src/use-state.spec.ts +105 -0
  33. package/src/use-state.ts +28 -0
  34. package/tsconfig.json +33 -0
  35. package/tsconfig.lib.json +42 -0
  36. package/tsconfig.lib.prod.json +10 -0
  37. package/tsconfig.spec.json +23 -0
  38. package/vite.config.mts +55 -0
  39. package/fesm2022/aiao-rxdb-angular.mjs +0 -47
  40. package/fesm2022/aiao-rxdb-angular.mjs.map +0 -1
  41. package/index.d.ts +0 -15
@@ -0,0 +1,47 @@
1
+ import { JsonPipe } from '@angular/common';
2
+ import { afterNextRender, Component, ElementRef, inject, input } from '@angular/core';
3
+ import { RxDBEntityChangeDirective } from '../rxdb-change-detector.directive';
4
+ import { ScrollAdvancedCalcService } from './scroll-advanced-calc.service';
5
+
6
+ @Component({
7
+ selector: 'rxdb-entity-item',
8
+ template: `
9
+ @let item = entity();
10
+ @let i = index();
11
+
12
+ <pre class="text-xs" [class.bg-base-100]="i % 2 === 0" [rxdbChangeDetector]="item">
13
+ {{ item | json }}
14
+ </pre
15
+ >
16
+ `,
17
+ imports: [JsonPipe, RxDBEntityChangeDirective],
18
+ styles: [
19
+ `
20
+ :host {
21
+ display: block;
22
+ min-height: 150px;
23
+ }
24
+ `
25
+ ]
26
+ })
27
+ export class RxDBEntityTableItem {
28
+ readonly #calcService = inject(ScrollAdvancedCalcService);
29
+ readonly #el = inject(ElementRef);
30
+ readonly entity = input.required<any>();
31
+ readonly index = input.required<number>();
32
+
33
+ constructor() {
34
+ afterNextRender(() => {
35
+ const item = this.entity();
36
+ this.#calcService.cacheCalcDynamic.update(cache => {
37
+ if (!cache.some(c => c.trackId === item.id)) {
38
+ cache.push({
39
+ trackId: item.id,
40
+ itemSize: this.#el.nativeElement.getBoundingClientRect().height
41
+ });
42
+ }
43
+ return cache;
44
+ });
45
+ });
46
+ }
47
+ }
@@ -0,0 +1,56 @@
1
+ @let entity_meta_tree = $entity_meta_tree();
2
+ @let current_entity_meta = $current_entity_meta();
3
+
4
+ <div class="bg-base-200 overflow-y-auto">
5
+ <ul class="menu menu-xs rounded-box w-30 p-0">
6
+ @for (ns of entity_meta_tree; track $index) {
7
+ <li>
8
+ <details open>
9
+ <summary>{{ ns.namespace }}</summary>
10
+ <ul>
11
+ @for (entity of ns.entities; track entity.name) {
12
+ <li>
13
+ <a
14
+ [class.menu-active]="current_entity_meta === entity"
15
+ (click)="$current_entity_meta.set(entity)"
16
+ aria-hidden="true"
17
+ >
18
+ {{ entity.name }}
19
+ </a>
20
+ </li>
21
+ }
22
+ </ul>
23
+ </details>
24
+ </li>
25
+ }
26
+ </ul>
27
+ </div>
28
+
29
+ <div class="relative flex-1 overflow-hidden">
30
+ <div class="tabs tabs-border tabs-xs bg-base-300 h-full">
31
+ <input class="tab" aria-label="Data" checked="checked" name="my_tabs_2" type="radio" />
32
+ <div class="tab-content h-full overflow-hidden">
33
+ <cdk-virtual-scroll-viewport
34
+ class="tab-content h-full"
35
+ [itemDynamicSizes]="dynamicSize()"
36
+ maxBufferPx="1350"
37
+ minBufferPx="900"
38
+ rdlaboFixVirtualScrollElement
39
+ >
40
+ @defer (on viewport) {
41
+ <ng-container *cdkVirtualFor="let item of $entity_all_data(); let i = index; trackBy: trackByFn">
42
+ <rxdb-entity-item [entity]="item" [index]="i"></rxdb-entity-item>
43
+ </ng-container>
44
+ } @placeholder {
45
+ <div class="h-40"></div>
46
+ }
47
+ </cdk-virtual-scroll-viewport>
48
+ </div>
49
+ <input class="tab" aria-label="Metadata" name="my_tabs_2" type="radio" />
50
+ <div class="tab-content h-full overflow-y-auto">
51
+ @if (current_entity_meta) {
52
+ <ao-code-editor [theme]="theme()" [value]="current_entity_meta | json" language="json"></ao-code-editor>
53
+ }
54
+ </div>
55
+ </div>
56
+ </div>
@@ -0,0 +1,8 @@
1
+ :host {
2
+ @apply flex h-full w-full overflow-hidden;
3
+ }
4
+
5
+ .menu :where(li ul) {
6
+ margin-inline-start: calc(0.25rem * 2);
7
+ padding-inline-start: calc(0.25rem * 0);
8
+ }
@@ -0,0 +1,153 @@
1
+ import { CodeEditor } from '@aiao/code-editor-angular';
2
+ import { EntityMetadata, EntityType, getEntityMetadata, RxDB } from '@aiao/rxdb';
3
+ import { nextMacroTask } from '@aiao/utils';
4
+ import { ObserversModule } from '@angular/cdk/observers';
5
+ import { CdkVirtualForOf, CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
6
+ import { CommonModule } from '@angular/common';
7
+ import {
8
+ ChangeDetectionStrategy,
9
+ ChangeDetectorRef,
10
+ Component,
11
+ computed,
12
+ effect,
13
+ inject,
14
+ input,
15
+ OnInit,
16
+ signal,
17
+ viewChild
18
+ } from '@angular/core';
19
+ import { toObservable, toSignal } from '@angular/core/rxjs-interop';
20
+ import {
21
+ CdkDynamicSizeVirtualScroll,
22
+ DynamicSizeVirtualScrollService,
23
+ itemDynamicSize
24
+ } from '@rdlabo/ngx-cdk-scroll-strategies';
25
+ import { LucideAngularModule } from 'lucide-angular';
26
+ import { of, switchMap } from 'rxjs';
27
+ import { RxDBEntityTableItem } from './entity-table-item';
28
+ import { ScrollAdvancedCalcService } from './scroll-advanced-calc.service';
29
+
30
+ interface EntityTreeItem {
31
+ namespace: string;
32
+ entities: EntityMetadata[];
33
+ }
34
+ (BigInt.prototype as any)['toJSON'] = function () {
35
+ return Number(this);
36
+ };
37
+ @Component({
38
+ selector: 'rxdb-entity-table-tools',
39
+ imports: [
40
+ LucideAngularModule,
41
+ CommonModule,
42
+ RxDBEntityTableItem,
43
+ CodeEditor,
44
+ ObserversModule,
45
+ CdkVirtualForOf,
46
+ CdkVirtualScrollViewport,
47
+ CdkDynamicSizeVirtualScroll
48
+ ],
49
+ changeDetection: ChangeDetectionStrategy.OnPush,
50
+ styleUrls: ['./entity-table-tools.scss'],
51
+ templateUrl: './entity-table-tools.html'
52
+ })
53
+ export class RxDBEntityTableTools implements OnInit {
54
+ #rxdb = inject(RxDB);
55
+ #entity_metadata_map = new Map<EntityMetadata, EntityType>();
56
+ #scroll = inject(DynamicSizeVirtualScrollService);
57
+ #calcService = inject(ScrollAdvancedCalcService);
58
+ #latestScrollOffset = 0;
59
+
60
+ virtualScroll = viewChild(CdkVirtualScrollViewport);
61
+
62
+ dynamicSize = computed<itemDynamicSize[]>(() => {
63
+ const size = this.#calcService.changeItemsToDynamicItemSize(
64
+ this.$entity_all_data() as any,
65
+ this.#calcService.cacheCalcDynamic(),
66
+ this.virtualScroll()
67
+ );
68
+ return size;
69
+ });
70
+
71
+ cdr = inject(ChangeDetectorRef);
72
+ theme = input<'light' | 'dark'>('light');
73
+
74
+ $entity_meta_tree = signal<EntityTreeItem[]>([]);
75
+ $current_entity_meta = signal<EntityMetadata | null>(null);
76
+
77
+ current_entity_meta$ = toObservable(this.$current_entity_meta);
78
+
79
+ entity_all_data$ = this.current_entity_meta$.pipe(
80
+ switchMap(meta => {
81
+ if (meta) {
82
+ const entity = this.#entity_metadata_map.get(meta)!;
83
+ return this.#rxdb.entityManager.getRepository(entity).findAll({
84
+ where: {
85
+ combinator: 'and',
86
+ rules: []
87
+ },
88
+ orderBy: [
89
+ {
90
+ field: 'id',
91
+ sort: 'desc'
92
+ }
93
+ ]
94
+ });
95
+ } else {
96
+ return of([]);
97
+ }
98
+ })
99
+ );
100
+
101
+ $entity_all_data = toSignal(this.entity_all_data$);
102
+ constructor() {
103
+ effect(() => {
104
+ this.$current_entity_meta();
105
+ nextMacroTask(() => {
106
+ this.#calcService.beforeCacheCalcDynamicSize.set(0);
107
+ this.#calcService.cacheCalcDynamic.set([]);
108
+ this.virtualScroll()?.scrollTo({
109
+ top: 0
110
+ });
111
+ });
112
+ });
113
+ }
114
+ trackByFn = (_: number, item: any) => {
115
+ let track_id = item.id;
116
+ if (item.updatedAt) {
117
+ track_id += (item.updatedAt as Date).getTime();
118
+ }
119
+ return track_id;
120
+ };
121
+
122
+ ngOnInit(): void {
123
+ this.#scroll.onInit(this.virtualScroll()!, this.#latestScrollOffset);
124
+ this.virtualScroll()?.scrolledIndexChange.subscribe(() => {
125
+ if (this.#calcService.beforeCacheCalcDynamicSize() !== this.#calcService.cacheCalcDynamic().length) {
126
+ this.#calcService.cacheCalcDynamic.update(cache => [...cache]);
127
+ this.#calcService.beforeCacheCalcDynamicSize.set(this.#calcService.cacheCalcDynamic().length);
128
+ }
129
+ });
130
+
131
+ const entity_meta_tree: EntityTreeItem[] = [];
132
+ let rxdb_change_meta: any;
133
+ this.#rxdb.config.entities.forEach(entity => {
134
+ const meta = getEntityMetadata(entity);
135
+ this.#entity_metadata_map.set(meta, entity);
136
+ if (meta.name === 'RxDBChange') {
137
+ rxdb_change_meta = meta;
138
+ }
139
+ const namespace = meta.namespace;
140
+ const existing = entity_meta_tree.find(item => item.namespace === namespace);
141
+ if (existing) {
142
+ existing.entities.push(meta);
143
+ } else {
144
+ entity_meta_tree.push({
145
+ namespace,
146
+ entities: [meta]
147
+ });
148
+ }
149
+ });
150
+ this.$entity_meta_tree.set(entity_meta_tree);
151
+ this.$current_entity_meta.set(rxdb_change_meta);
152
+ }
153
+ }
@@ -0,0 +1,15 @@
1
+ @let events = $events();
2
+
3
+ <div class="bg-base-300 w-full text-xs">
4
+ <button class="btn btn-xs" (click)="clear()">clear</button>
5
+ <span>{{ events.length }}</span>
6
+ </div>
7
+ <div class="bg-base-300 relative flex-1 overflow-y-auto">
8
+ @for (item of events; track item) {
9
+ @defer (on viewport) {
10
+ <pre class="text-xs" [class.bg-base-100]="$index % 2 === 0" animate.enter="fade-in">{{ item | json }}</pre>
11
+ } @placeholder {
12
+ <div class="h-40">loading...</div>
13
+ }
14
+ }
15
+ </div>
@@ -0,0 +1,18 @@
1
+ :host {
2
+ @apply flex h-full w-full flex-col overflow-hidden;
3
+ }
4
+
5
+ .fade-in {
6
+ animation: fadeIn 0.3s ease-out;
7
+ }
8
+
9
+ @keyframes fadeIn {
10
+ from {
11
+ opacity: 0;
12
+ transform: translateX(100px);
13
+ }
14
+ to {
15
+ opacity: 1;
16
+ transform: translateX(0);
17
+ }
18
+ }
@@ -0,0 +1,45 @@
1
+ import {
2
+ ENTITY_LOCAL_CREATE_EVENT,
3
+ ENTITY_LOCAL_NEW_EVENT,
4
+ ENTITY_LOCAL_REMOVE_EVENT,
5
+ ENTITY_LOCAL_UPDATE_EVENT,
6
+ RxDB,
7
+ RxDBEvent,
8
+ TRANSACTION_BEGIN,
9
+ TRANSACTION_COMMIT,
10
+ TRANSACTION_ROLLBACK
11
+ } from '@aiao/rxdb';
12
+ import { JsonPipe } from '@angular/common';
13
+ import { ChangeDetectionStrategy, Component, inject, OnInit, signal } from '@angular/core';
14
+ import { LucideAngularModule } from 'lucide-angular';
15
+
16
+ @Component({
17
+ selector: 'rxdb-event-tools',
18
+ imports: [LucideAngularModule, JsonPipe],
19
+ changeDetection: ChangeDetectionStrategy.OnPush,
20
+ styleUrls: ['./event-tools.scss'],
21
+ templateUrl: './event-tools.html'
22
+ })
23
+ export class RxDBEventTools implements OnInit {
24
+ #rxdb = inject(RxDB);
25
+
26
+ $events = signal<RxDBEvent[]>([]);
27
+
28
+ ngOnInit(): void {
29
+ this.#rxdb.addEventListener(ENTITY_LOCAL_NEW_EVENT, this.handler);
30
+ this.#rxdb.addEventListener(ENTITY_LOCAL_CREATE_EVENT, this.handler);
31
+ this.#rxdb.addEventListener(ENTITY_LOCAL_UPDATE_EVENT, this.handler);
32
+ this.#rxdb.addEventListener(ENTITY_LOCAL_REMOVE_EVENT, this.handler);
33
+ this.#rxdb.addEventListener(TRANSACTION_BEGIN, this.handler);
34
+ this.#rxdb.addEventListener(TRANSACTION_COMMIT, this.handler);
35
+ this.#rxdb.addEventListener(TRANSACTION_ROLLBACK, this.handler);
36
+ }
37
+
38
+ handler = (event: RxDBEvent) => {
39
+ this.$events.update(events => [event, ...events]);
40
+ };
41
+
42
+ clear() {
43
+ this.$events.set([]);
44
+ }
45
+ }
@@ -0,0 +1,41 @@
1
+ import { CdkVirtualScrollViewport } from '@angular/cdk/scrolling';
2
+ import { Injectable, signal } from '@angular/core';
3
+ import { itemDynamicSize } from '@rdlabo/ngx-cdk-scroll-strategies';
4
+
5
+ export interface ScrollAdvancedItem {
6
+ id: string;
7
+ }
8
+
9
+ export interface DynamicSizeCache {
10
+ trackId: number | string;
11
+ itemSize: number;
12
+ }
13
+
14
+ @Injectable({
15
+ providedIn: 'root'
16
+ })
17
+ export class ScrollAdvancedCalcService {
18
+ readonly cacheCalcDynamic = signal<DynamicSizeCache[]>([]);
19
+ readonly beforeCacheCalcDynamicSize = signal<number>(0);
20
+
21
+ changeItemsToDynamicItemSize(
22
+ items: ScrollAdvancedItem[],
23
+ dynamicSizeCache: DynamicSizeCache[],
24
+ virtualScroll: CdkVirtualScrollViewport | undefined
25
+ ): itemDynamicSize[] {
26
+ if (virtualScroll === undefined) {
27
+ return [];
28
+ }
29
+ return (
30
+ items?.map(item => {
31
+ const cacheSize = dynamicSizeCache.find(cache => cache.trackId === item.id)?.itemSize;
32
+ const itemSize = cacheSize || 150;
33
+ return {
34
+ itemSize: Math.ceil(itemSize),
35
+ trackId: item.id,
36
+ source: cacheSize !== undefined ? 'cache' : 'temporary'
37
+ };
38
+ }) || []
39
+ );
40
+ }
41
+ }
@@ -0,0 +1,46 @@
1
+ <div class="p-2">
2
+ <fieldset class="fieldset">
3
+ <legend class="fieldset-legend">Side</legend>
4
+ <div class="join" id="side">
5
+ <button
6
+ class="btn join-item btn-xs btn-ghost"
7
+ id="panel-left"
8
+ (click)="sideChange.emit('left')"
9
+ aria-hidden="true"
10
+ >
11
+ <lucide-icon [img]="PanelLeft" size="16"></lucide-icon>
12
+ </button>
13
+ <button
14
+ class="btn join-item btn-xs btn-ghost"
15
+ id="panel-bottom"
16
+ (click)="sideChange.emit('bottom')"
17
+ aria-hidden="true"
18
+ >
19
+ <lucide-icon [img]="PanelBottom" size="16"></lucide-icon>
20
+ </button>
21
+ <button
22
+ class="btn join-item btn-xs btn-ghost"
23
+ id="panel-right"
24
+ (click)="sideChange.emit('right')"
25
+ aria-hidden="true"
26
+ >
27
+ <lucide-icon [img]="PanelRight" size="16"></lucide-icon>
28
+ </button>
29
+ </div>
30
+ </fieldset>
31
+
32
+ <fieldset class="fieldset">
33
+ <legend class="fieldset-legend">tools</legend>
34
+ <div class="flex gap-2 p-2">
35
+ <button class="btn btn-xs" (click)="delete_db()">delete_db</button>
36
+ </div>
37
+ </fieldset>
38
+ </div>
39
+
40
+ <ng-template #message let-message="message">
41
+ <div class="toast toast-top toast-center">
42
+ <div class="alert alert-error">
43
+ <span>{{ message }}</span>
44
+ </div>
45
+ </div>
46
+ </ng-template>
@@ -0,0 +1,19 @@
1
+ :host {
2
+ @apply flex h-full w-full flex-col overflow-hidden;
3
+ }
4
+
5
+ :has(rxdb-devtools.right) :host {
6
+ #panel-right {
7
+ color: var(--color-primary);
8
+ }
9
+ }
10
+ :has(rxdb-devtools.left) :host {
11
+ #panel-left {
12
+ color: var(--color-primary);
13
+ }
14
+ }
15
+ :has(rxdb-devtools.bottom) :host {
16
+ #panel-bottom {
17
+ color: var(--color-primary);
18
+ }
19
+ }
@@ -0,0 +1,122 @@
1
+ import { RxDB, RxDBEvent } from '@aiao/rxdb';
2
+ import {
3
+ ChangeDetectionStrategy,
4
+ Component,
5
+ inject,
6
+ output,
7
+ signal,
8
+ TemplateRef,
9
+ viewChild,
10
+ ViewContainerRef
11
+ } from '@angular/core';
12
+ import { LucideAngularModule, PanelBottom, PanelLeft, PanelRight } from 'lucide-angular';
13
+
14
+ interface FileEntry {
15
+ name: string;
16
+ path: string;
17
+ type: 'file' | 'directory';
18
+ handle: FileSystemDirectoryHandle | FileSystemFileHandle;
19
+ }
20
+
21
+ @Component({
22
+ selector: 'rxdb-settings',
23
+ imports: [LucideAngularModule],
24
+ changeDetection: ChangeDetectionStrategy.OnPush,
25
+ styleUrls: ['./settings.scss'],
26
+ templateUrl: './settings.html'
27
+ })
28
+ export class RxDBSettings {
29
+ #viewContainerRef = inject(ViewContainerRef);
30
+ #rxdb = inject(RxDB);
31
+
32
+ protected readonly PanelLeft = PanelLeft;
33
+ protected readonly PanelBottom = PanelBottom;
34
+ protected readonly PanelRight = PanelRight;
35
+
36
+ messageTmp = viewChild<TemplateRef<any>>('message');
37
+
38
+ $events = signal<RxDBEvent[]>([]);
39
+
40
+ sideChange = output<'right' | 'left' | 'bottom'>();
41
+
42
+ async delete_db() {
43
+ try {
44
+ await this.#rxdb.disconnectAll();
45
+ } catch {
46
+ //
47
+ }
48
+
49
+ try {
50
+ const root = await navigator.storage.getDirectory();
51
+ const files = await this.listAllFiles(root, 1);
52
+ for (const entry of files) {
53
+ if (entry.type === 'file') {
54
+ await root.removeEntry(entry.name, { recursive: true });
55
+ }
56
+ }
57
+ await this.clean_indexedDB();
58
+ localStorage.clear();
59
+ // 使用 baseURI 跳转回首页,避免在子路由刷新时因服务器配置问题导致 404 fallback 到主站
60
+ window.location.href = document.baseURI;
61
+ } catch (error: Error | any) {
62
+ if (error && error.name === 'NoModificationAllowedError') {
63
+ const ref = this.#viewContainerRef.createEmbeddedView<{ message: string }>(this.messageTmp()!, {
64
+ message: '关闭数据库失败,请检查是否在多个浏览器 tab 中打开页面'
65
+ });
66
+ setTimeout(() => {
67
+ ref.destroy();
68
+ }, 3000);
69
+ }
70
+ console.error(error);
71
+ }
72
+ }
73
+
74
+ async clean_indexedDB() {
75
+ const dbList = indexedDB.databases ? await indexedDB.databases() : [];
76
+ if (dbList.length) {
77
+ await Promise.all(
78
+ dbList.map(({ name }) => {
79
+ return new Promise<void>((resolve, reject) => {
80
+ const request = indexedDB.deleteDatabase(name!);
81
+ request.onsuccess = () => resolve();
82
+ request.onerror = () => reject(request.error);
83
+ request.onblocked = () => resolve();
84
+ });
85
+ })
86
+ );
87
+ }
88
+ }
89
+
90
+ async listAllFiles(
91
+ directoryHandle: FileSystemDirectoryHandle | any,
92
+ maxDeep = Number.MAX_SAFE_INTEGER,
93
+ path = '',
94
+ currentDeep = 1
95
+ ) {
96
+ const entries: FileEntry[] = [];
97
+ for await (const [name, handle] of directoryHandle) {
98
+ const entryPath = path ? `${path}/${name}` : name;
99
+ if (handle.kind === 'file') {
100
+ entries.push({
101
+ name: name,
102
+ path: entryPath,
103
+ type: 'file',
104
+ handle: handle
105
+ });
106
+ } else if (handle.kind === 'directory') {
107
+ entries.push({
108
+ name: name,
109
+ path: entryPath,
110
+ type: 'directory',
111
+ handle: handle
112
+ });
113
+
114
+ if (currentDeep < maxDeep) {
115
+ const subEntries = await this.listAllFiles(handle, maxDeep, entryPath, currentDeep + 1);
116
+ entries.push(...subEntries);
117
+ }
118
+ }
119
+ }
120
+ return entries;
121
+ }
122
+ }