@asaidimu/hestia 1.0.0 → 1.0.2
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.md +22 -0
- package/index.cjs +8 -0
- package/index.d.cts +920 -0
- package/index.d.mts +920 -0
- package/index.mjs +8 -0
- package/package.json +28 -21
- package/auth/store.ts +0 -75
- package/auth/types.ts +0 -38
- package/blobs/store.ts +0 -208
- package/blobs/types.ts +0 -28
- package/collections/store.ts +0 -59
- package/collections/types.ts +0 -11
- package/container.ts +0 -95
- package/core/client.ts +0 -247
- package/core/collection.ts +0 -121
- package/core/errors.ts +0 -55
- package/core/pager.ts +0 -159
- package/core/types.ts +0 -279
- package/index.ts +0 -62
- package/system/api-keys/store.ts +0 -70
- package/system/api-keys/types.ts +0 -29
- package/system/capabilities/store.ts +0 -23
- package/system/capabilities/types.ts +0 -1
- package/system/identity/store.ts +0 -90
- package/system/identity/types.ts +0 -31
- package/system/logs/store.ts +0 -147
- package/system/logs/types.ts +0 -38
- package/system/policies/store.ts +0 -74
- package/system/policies/types.ts +0 -43
- package/test-setup.ts +0 -53
- package/utils/index.ts +0 -1
- package/utils/pager.ts +0 -226
- package/vitest.config.ts +0 -8
package/system/logs/store.ts
DELETED
|
@@ -1,147 +0,0 @@
|
|
|
1
|
-
import type { QueryDSL } from "@asaidimu/query"
|
|
2
|
-
import { HestiaNetworkClient } from "../../core/client"
|
|
3
|
-
import type { Page, PaginationInfo } from "../../core/types"
|
|
4
|
-
import type { AuditEntry } from "./types"
|
|
5
|
-
|
|
6
|
-
interface FindEnvelope {
|
|
7
|
-
data: AuditEntry[]
|
|
8
|
-
metadata?: { page?: PaginationInfo }
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
interface StreamDoc {
|
|
12
|
-
data: AuditEntry
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export class HestiaLogs {
|
|
16
|
-
constructor(
|
|
17
|
-
private client: HestiaNetworkClient,
|
|
18
|
-
private baseUrl: string,
|
|
19
|
-
) {}
|
|
20
|
-
|
|
21
|
-
async find(query?: QueryDSL<AuditEntry>): Promise<Page<AuditEntry>> {
|
|
22
|
-
const res = await this.client.post<FindEnvelope>(
|
|
23
|
-
"/system/audit/log/query",
|
|
24
|
-
{ query: query ?? {} },
|
|
25
|
-
)
|
|
26
|
-
|
|
27
|
-
const items = res.data?.data ?? []
|
|
28
|
-
const pageMeta = res.data?.metadata?.page ?? {
|
|
29
|
-
number: 1,
|
|
30
|
-
size: items.length,
|
|
31
|
-
count: items.length,
|
|
32
|
-
total: items.length,
|
|
33
|
-
pages: 1,
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
return { data: items, loading: false, page: pageMeta, error: null }
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async list(): Promise<Page<AuditEntry>> {
|
|
40
|
-
return this.find({
|
|
41
|
-
pagination: { type: "offset", offset: 0, limit: 20 },
|
|
42
|
-
})
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
getStreamUrl(): string {
|
|
46
|
-
return `${this.baseUrl}/system/audit/log/stream`
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
subscribe(onEntry: (entry: AuditEntry) => void): () => void {
|
|
50
|
-
const url = this.getStreamUrl()
|
|
51
|
-
const eventSource = new EventSource(url, { withCredentials: true })
|
|
52
|
-
|
|
53
|
-
eventSource.onmessage = (event) => {
|
|
54
|
-
try {
|
|
55
|
-
const parsed = JSON.parse(event.data) as StreamDoc
|
|
56
|
-
if (parsed?.data) {
|
|
57
|
-
onEntry(parsed.data)
|
|
58
|
-
}
|
|
59
|
-
} catch {
|
|
60
|
-
// skip non-JSON lines
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
eventSource.onerror = () => {
|
|
65
|
-
eventSource.close()
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
return () => {
|
|
69
|
-
eventSource.close()
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
stream(): {
|
|
74
|
-
stream: () => AsyncIterable<AuditEntry>
|
|
75
|
-
cancel: () => void
|
|
76
|
-
status: () => "active" | "cancelled" | "completed"
|
|
77
|
-
} {
|
|
78
|
-
const url = this.getStreamUrl()
|
|
79
|
-
let eventSource: EventSource | null = null
|
|
80
|
-
let currentStatus: "active" | "cancelled" | "completed" = "active"
|
|
81
|
-
let pendingResolve: (() => void) | null = null
|
|
82
|
-
|
|
83
|
-
const asyncStream = async function* () {
|
|
84
|
-
const pending: AuditEntry[] = []
|
|
85
|
-
|
|
86
|
-
eventSource = new EventSource(url, { withCredentials: true })
|
|
87
|
-
|
|
88
|
-
eventSource.onmessage = (event) => {
|
|
89
|
-
for (const line of event.data.split("\n")) {
|
|
90
|
-
const trimmed = line.trim()
|
|
91
|
-
if (!trimmed) continue
|
|
92
|
-
try {
|
|
93
|
-
const parsed = JSON.parse(trimmed) as StreamDoc
|
|
94
|
-
if (parsed?.data) pending.push(parsed.data)
|
|
95
|
-
} catch {
|
|
96
|
-
// skip non-JSON lines
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
if (pending.length > 0 && pendingResolve) {
|
|
100
|
-
pendingResolve()
|
|
101
|
-
pendingResolve = null
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
eventSource.onerror = () => {
|
|
106
|
-
if (currentStatus === "active") currentStatus = "completed"
|
|
107
|
-
if (pendingResolve) {
|
|
108
|
-
pendingResolve()
|
|
109
|
-
pendingResolve = null
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
try {
|
|
114
|
-
while (currentStatus === "active") {
|
|
115
|
-
if (pending.length > 0) {
|
|
116
|
-
yield pending.shift()!
|
|
117
|
-
} else {
|
|
118
|
-
await new Promise<void>((resolve) => {
|
|
119
|
-
pendingResolve = resolve
|
|
120
|
-
if (pending.length > 0) {
|
|
121
|
-
resolve()
|
|
122
|
-
pendingResolve = null
|
|
123
|
-
}
|
|
124
|
-
})
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
} finally {
|
|
128
|
-
eventSource?.close()
|
|
129
|
-
if (pendingResolve) {
|
|
130
|
-
pendingResolve()
|
|
131
|
-
pendingResolve = null
|
|
132
|
-
}
|
|
133
|
-
if (currentStatus === "active") currentStatus = "completed"
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
return {
|
|
138
|
-
stream: () => asyncStream(),
|
|
139
|
-
cancel: () => {
|
|
140
|
-
if (currentStatus !== "active") return
|
|
141
|
-
currentStatus = "cancelled"
|
|
142
|
-
eventSource?.close()
|
|
143
|
-
},
|
|
144
|
-
status: () => currentStatus,
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
}
|
package/system/logs/types.ts
DELETED
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
export interface AuditEntry {
|
|
2
|
-
event_id: string
|
|
3
|
-
occurred_at: string
|
|
4
|
-
recorded_at: string
|
|
5
|
-
trace_id?: string
|
|
6
|
-
request_id?: string
|
|
7
|
-
actor_id: string
|
|
8
|
-
actor_type: "user" | "service" | "system" | "anonymous"
|
|
9
|
-
on_behalf_of_id?: string
|
|
10
|
-
auth_method?: "password" | "oauth" | "api_key" | "mtls" | "sso" | "service_account" | "none"
|
|
11
|
-
session_id?: string
|
|
12
|
-
operation: "create" | "read" | "update" | "delete" | "login" | "logout" | "grant" | "revoke" | "execute" | "other"
|
|
13
|
-
resource_type: string
|
|
14
|
-
resource_id?: string
|
|
15
|
-
event_name: string
|
|
16
|
-
status: "success" | "failure" | "denied" | "error"
|
|
17
|
-
severity?: "info" | "warning" | "critical"
|
|
18
|
-
error_code?: string
|
|
19
|
-
error_message?: string
|
|
20
|
-
latency_ms: number
|
|
21
|
-
source_ip?: string
|
|
22
|
-
user_agent?: string
|
|
23
|
-
service_name: string
|
|
24
|
-
region?: string
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export type RequestLogEntry = AuditEntry
|
|
28
|
-
|
|
29
|
-
export interface LogFilter {
|
|
30
|
-
actor_id?: string
|
|
31
|
-
actor_type?: string
|
|
32
|
-
operation?: string
|
|
33
|
-
status?: string
|
|
34
|
-
resource_type?: string
|
|
35
|
-
trace_id?: string
|
|
36
|
-
start?: string
|
|
37
|
-
end?: string
|
|
38
|
-
}
|
package/system/policies/store.ts
DELETED
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
import type { QueryDSL } from "@asaidimu/query"
|
|
2
|
-
import { HestiaNetworkClient } from "../../core/client"
|
|
3
|
-
import { HestiaCollection } from "../../core/collection"
|
|
4
|
-
import type { Document, Page } from "../../core/types"
|
|
5
|
-
import type {
|
|
6
|
-
PolicyOperation,
|
|
7
|
-
PolicyRule,
|
|
8
|
-
UpsertOperationRequest,
|
|
9
|
-
UpsertRuleRequest,
|
|
10
|
-
} from "./types"
|
|
11
|
-
|
|
12
|
-
export class HestiaPolicies {
|
|
13
|
-
private operations: HestiaCollection<PolicyOperation>
|
|
14
|
-
private rules: HestiaCollection<PolicyRule>
|
|
15
|
-
|
|
16
|
-
constructor(private client: HestiaNetworkClient) {
|
|
17
|
-
this.operations = new HestiaCollection<PolicyOperation>(client, "_policy_operation_")
|
|
18
|
-
this.rules = new HestiaCollection<PolicyRule>(client, "_policy_rule_")
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
// ── Operations ──
|
|
22
|
-
|
|
23
|
-
async findOperations(query?: QueryDSL<PolicyOperation>): Promise<Page<PolicyOperation>> {
|
|
24
|
-
return this.operations.find(query)
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
async listOperations(): Promise<Page<PolicyOperation>> {
|
|
28
|
-
return this.operations.find({ pagination: { type: "offset", offset: 0, limit: 50 } })
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
async readOperation(name: string): Promise<Document<PolicyOperation> | undefined> {
|
|
32
|
-
return this.operations.read(name)
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async createOperation(data: UpsertOperationRequest): Promise<Document<PolicyOperation>> {
|
|
36
|
-
return this.operations.create(data as any)
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
async updateOperation(name: string, data: UpsertOperationRequest): Promise<Document<PolicyOperation>> {
|
|
40
|
-
return this.operations.update(name, data as any)
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
async deleteOperation(name: string): Promise<void> {
|
|
44
|
-
return this.operations.delete(name)
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
// ── Rules ──
|
|
48
|
-
|
|
49
|
-
async findRules(query?: QueryDSL<PolicyRule>): Promise<Page<PolicyRule>> {
|
|
50
|
-
return this.rules.find(query)
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
async listRules(): Promise<Page<PolicyRule>> {
|
|
54
|
-
return this.rules.find({ pagination: { type: "offset", offset: 0, limit: 50 } })
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async readRule(name: string): Promise<Document<PolicyRule> | undefined> {
|
|
58
|
-
return this.rules.read(name)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async createRule(data: UpsertRuleRequest): Promise<Document<PolicyRule>> {
|
|
62
|
-
return this.rules.create(data as any)
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
async updateRule(name: string, data: UpsertRuleRequest): Promise<Document<PolicyRule>> {
|
|
66
|
-
return this.rules.update(name, data as any)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
async deleteRule(name: string): Promise<void> {
|
|
70
|
-
return this.rules.delete(name)
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
package/system/policies/types.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
export interface PolicyOperation {
|
|
2
|
-
name: string
|
|
3
|
-
ruleKey: string
|
|
4
|
-
description?: string
|
|
5
|
-
intentType?: "COMMAND" | "QUERY"
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export interface UpsertOperationRequest {
|
|
9
|
-
ruleKey: string
|
|
10
|
-
description?: string
|
|
11
|
-
intentType?: "COMMAND" | "QUERY"
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface PolicyRule {
|
|
15
|
-
name: string
|
|
16
|
-
ruleType?: string
|
|
17
|
-
expression?: string
|
|
18
|
-
description?: string
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export interface UpsertRuleRequest {
|
|
22
|
-
expression: string
|
|
23
|
-
ruleType?: string
|
|
24
|
-
description?: string
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface ValidateRuleRequest {
|
|
28
|
-
expression: string
|
|
29
|
-
identity?: Record<string, unknown>
|
|
30
|
-
resource?: Record<string, unknown>
|
|
31
|
-
environment?: Record<string, unknown>
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface ValidateRuleResult {
|
|
35
|
-
valid: boolean
|
|
36
|
-
compile?: string
|
|
37
|
-
result?: boolean
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export interface ReloadResult {
|
|
41
|
-
operations: number
|
|
42
|
-
rules: number
|
|
43
|
-
}
|
package/test-setup.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { spawn, type ChildProcess } from "child_process"
|
|
2
|
-
import { resolve } from "path"
|
|
3
|
-
|
|
4
|
-
const PROJECT_ROOT = resolve(import.meta.dirname, "..")
|
|
5
|
-
const SERVER_BIN = resolve(PROJECT_ROOT, "test-server")
|
|
6
|
-
const START_TIMEOUT = 30000
|
|
7
|
-
|
|
8
|
-
let serverProc: ChildProcess | null = null
|
|
9
|
-
|
|
10
|
-
function sleep(ms: number): Promise<void> {
|
|
11
|
-
return new Promise((r) => setTimeout(r, ms))
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export async function setup(): Promise<void> {
|
|
15
|
-
await new Promise<void>((resolvePromise, reject) => {
|
|
16
|
-
serverProc = spawn(SERVER_BIN, [], {
|
|
17
|
-
cwd: PROJECT_ROOT,
|
|
18
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
19
|
-
})
|
|
20
|
-
|
|
21
|
-
let started = false
|
|
22
|
-
const onData = (data: Buffer) => {
|
|
23
|
-
const text = data.toString().trim()
|
|
24
|
-
if (!started && /^\d+$/.test(text)) {
|
|
25
|
-
started = true
|
|
26
|
-
resolvePromise()
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
serverProc.stdout!.on("data", onData)
|
|
31
|
-
serverProc.stderr!.on("data", () => {})
|
|
32
|
-
|
|
33
|
-
serverProc.on("error", (err) => {
|
|
34
|
-
if (!started) reject(err)
|
|
35
|
-
})
|
|
36
|
-
serverProc.on("exit", (code) => {
|
|
37
|
-
if (!started) reject(new Error(`server exited with code ${code} before ready`))
|
|
38
|
-
})
|
|
39
|
-
|
|
40
|
-
setTimeout(() => {
|
|
41
|
-
if (!started) reject(new Error("server start timeout"))
|
|
42
|
-
}, START_TIMEOUT)
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
await sleep(1000)
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
export async function teardown(): Promise<void> {
|
|
49
|
-
if (serverProc && !serverProc.killed) {
|
|
50
|
-
serverProc.kill("SIGTERM")
|
|
51
|
-
serverProc = null
|
|
52
|
-
}
|
|
53
|
-
}
|
package/utils/index.ts
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./pager"
|
package/utils/pager.ts
DELETED
|
@@ -1,226 +0,0 @@
|
|
|
1
|
-
import { QueryDSL, QueryFilter, SortConfiguration, createMatcher, createSorter } from '@asaidimu/query'; //
|
|
2
|
-
import { DELETE_SYMBOL, ReactiveDataStore } from "@asaidimu/utils-store"; //
|
|
3
|
-
import { Page, PagedData } from '../core/types';
|
|
4
|
-
|
|
5
|
-
export interface FetchResult<T extends Record<string, any>> {
|
|
6
|
-
data: T[];
|
|
7
|
-
replace: boolean;
|
|
8
|
-
scope: 'page' | 'collection';
|
|
9
|
-
total?: number;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
// Extensible parameters object embracing your DataStore infra
|
|
13
|
-
export interface ArrayPagerOptions<T extends Record<string, any>, F = any> {
|
|
14
|
-
collectionName: string; // Unique identifier used to partition the cache matrix
|
|
15
|
-
initialData?: T[];
|
|
16
|
-
page?: number;
|
|
17
|
-
size?: number;
|
|
18
|
-
customFunctions?: Record<string, (...args: any[]) => boolean>;
|
|
19
|
-
fetch?: (
|
|
20
|
-
query: QueryDSL<T, F>
|
|
21
|
-
) => Promise<FetchResult<T>> | FetchResult<T>;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function createArrayPager<T extends Record<string, any>, F = any>(
|
|
25
|
-
options: ArrayPagerOptions<T, F>
|
|
26
|
-
): PagedData<T> {
|
|
27
|
-
const CACHE_KEY = `${options.collectionName}_array_pager_state_`; //
|
|
28
|
-
const store = new ReactiveDataStore({})
|
|
29
|
-
|
|
30
|
-
// Baseline backing arrays managed by this adapter instance
|
|
31
|
-
let pristineData = [...(options.initialData ?? [])];
|
|
32
|
-
let activeScope: 'page' | 'collection' = 'collection';
|
|
33
|
-
let remoteTotalRecords: number | undefined = undefined;
|
|
34
|
-
|
|
35
|
-
// Track active filter parameters
|
|
36
|
-
let currentPageNum = options.page ?? 1;
|
|
37
|
-
let currentPageSize = options.size ?? 20; // Defaulting to 20 per your page controller sentinel
|
|
38
|
-
let currentSort: SortConfiguration<T>[] = [];
|
|
39
|
-
let currentFilter: QueryFilter<T> | undefined = undefined;
|
|
40
|
-
|
|
41
|
-
// Tracker for errors across async horizons
|
|
42
|
-
let operationalError: any = undefined;
|
|
43
|
-
|
|
44
|
-
const { match } = createMatcher<T, F>(options.customFunctions ?? {}); //
|
|
45
|
-
const { sort: localSort} = createSorter()
|
|
46
|
-
const selector = store.select((s: any) => s[CACHE_KEY]); //
|
|
47
|
-
|
|
48
|
-
// Default fallback layout conforming strictly to Page<T>
|
|
49
|
-
const sentinel: Page<T> = { //
|
|
50
|
-
data: [], //
|
|
51
|
-
loading: true, //
|
|
52
|
-
error: undefined,
|
|
53
|
-
page: { //
|
|
54
|
-
number: 1, //
|
|
55
|
-
size: currentPageSize, //
|
|
56
|
-
count: 0, //
|
|
57
|
-
total: 0, //
|
|
58
|
-
pages: 1, //
|
|
59
|
-
},
|
|
60
|
-
};
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Constructs the current active QueryDSL footprint.
|
|
64
|
-
*/
|
|
65
|
-
const buildQueryDSL = (): QueryDSL<T, F> => ({ //
|
|
66
|
-
filters: currentFilter, //
|
|
67
|
-
sort: currentSort, //
|
|
68
|
-
pagination: { //
|
|
69
|
-
type: 'offset', //
|
|
70
|
-
offset: (currentPageNum - 1) * currentPageSize, //
|
|
71
|
-
limit: currentPageSize //
|
|
72
|
-
}
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Orchestrates the fetching lifecycle and syncs results into the centralized store.
|
|
77
|
-
*/
|
|
78
|
-
const executeDataPipeline = async (isLoadingFallback = true) => {
|
|
79
|
-
if (options.fetch) {
|
|
80
|
-
operationalError = undefined;
|
|
81
|
-
|
|
82
|
-
if (isLoadingFallback) {
|
|
83
|
-
// Broadcast transitional loading state via the central store
|
|
84
|
-
const previousState = selector.get() ?? sentinel;
|
|
85
|
-
await store.set({
|
|
86
|
-
[CACHE_KEY]: { ...previousState, loading: true, error: undefined }
|
|
87
|
-
});
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
const queryDSL = buildQueryDSL();
|
|
92
|
-
const result = await options.fetch(queryDSL);
|
|
93
|
-
|
|
94
|
-
activeScope = result.scope;
|
|
95
|
-
remoteTotalRecords = result.total;
|
|
96
|
-
|
|
97
|
-
pristineData = result.replace
|
|
98
|
-
? [...result.data]
|
|
99
|
-
: [...pristineData, ...result.data];
|
|
100
|
-
} catch (error) {
|
|
101
|
-
operationalError = error;
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// Process and dispatch to store
|
|
106
|
-
const updatedPagePayload = computeProcessedPage(options.fetch ? isLoadingFallback : false);
|
|
107
|
-
await store.set({ [CACHE_KEY]: updatedPagePayload }); //
|
|
108
|
-
};
|
|
109
|
-
|
|
110
|
-
/**
|
|
111
|
-
* Coordinates local filtering, sorting and slicing transformations
|
|
112
|
-
* based on the active structural scope strategy.
|
|
113
|
-
*/
|
|
114
|
-
const computeProcessedPage = (isLoadingFlag: boolean): Page<T> => { //
|
|
115
|
-
let records = [...pristineData];
|
|
116
|
-
|
|
117
|
-
if (activeScope === 'collection') {
|
|
118
|
-
// Execute standard local processing passes over the full source collection
|
|
119
|
-
if (currentFilter) {
|
|
120
|
-
records = records.filter((item) => match(item, currentFilter!)); //
|
|
121
|
-
}
|
|
122
|
-
if (currentSort.length > 0) {
|
|
123
|
-
records = localSort(records, currentSort); //
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const total = records.length;
|
|
127
|
-
const pages = Math.ceil(total / currentPageSize) || 1;
|
|
128
|
-
|
|
129
|
-
if (currentPageNum > pages) currentPageNum = pages;
|
|
130
|
-
if (currentPageNum < 1) currentPageNum = 1;
|
|
131
|
-
|
|
132
|
-
const startIdx = (currentPageNum - 1) * currentPageSize;
|
|
133
|
-
const slicedData = records.slice(startIdx, startIdx + currentPageSize);
|
|
134
|
-
|
|
135
|
-
return {
|
|
136
|
-
data: slicedData as any,
|
|
137
|
-
loading: isLoadingFlag,
|
|
138
|
-
error: operationalError,
|
|
139
|
-
page: { //
|
|
140
|
-
number: currentPageNum,
|
|
141
|
-
size: currentPageSize,
|
|
142
|
-
count: slicedData.length,
|
|
143
|
-
total,
|
|
144
|
-
pages,
|
|
145
|
-
}
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
// Server-Driven mode: Data array has already been shaped for this target viewport
|
|
150
|
-
const total = remoteTotalRecords ?? records.length;
|
|
151
|
-
const pages = Math.ceil(total / currentPageSize) || 1;
|
|
152
|
-
|
|
153
|
-
return {
|
|
154
|
-
data: records as any,
|
|
155
|
-
loading: isLoadingFlag,
|
|
156
|
-
error: operationalError,
|
|
157
|
-
page: { //
|
|
158
|
-
number: currentPageNum,
|
|
159
|
-
size: currentPageSize,
|
|
160
|
-
count: records.length,
|
|
161
|
-
total,
|
|
162
|
-
pages,
|
|
163
|
-
}
|
|
164
|
-
};
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
// Kickstart immediate bootstrapping pass
|
|
168
|
-
executeDataPipeline(true);
|
|
169
|
-
|
|
170
|
-
return {
|
|
171
|
-
page: () => selector.get() ?? sentinel, //
|
|
172
|
-
|
|
173
|
-
navigate: async (page: number) => {
|
|
174
|
-
if (page < 1) throw new Error("Page number must be >= 1"); //
|
|
175
|
-
currentPageNum = page;
|
|
176
|
-
await executeDataPipeline(true);
|
|
177
|
-
},
|
|
178
|
-
|
|
179
|
-
resize: async (size: number, page: number) => {
|
|
180
|
-
if (size < 1) throw new Error("Page size must be >= 1"); //
|
|
181
|
-
currentPageSize = size;
|
|
182
|
-
currentPageNum = page;
|
|
183
|
-
await executeDataPipeline(true);
|
|
184
|
-
},
|
|
185
|
-
|
|
186
|
-
sort: async (sortConfig) => {
|
|
187
|
-
currentSort = Array.isArray(sortConfig) ? sortConfig : [sortConfig];
|
|
188
|
-
currentPageNum = 1; // Reset window index
|
|
189
|
-
await executeDataPipeline(true);
|
|
190
|
-
},
|
|
191
|
-
|
|
192
|
-
filter: async (queryFilter) => {
|
|
193
|
-
currentFilter = queryFilter;
|
|
194
|
-
currentPageNum = 1; // Reset window index
|
|
195
|
-
await executeDataPipeline(true);
|
|
196
|
-
},
|
|
197
|
-
|
|
198
|
-
refresh: async (delay: number = 600) => { //
|
|
199
|
-
const previousState = selector.get() ?? sentinel;
|
|
200
|
-
await store.set({
|
|
201
|
-
[CACHE_KEY]: { ...previousState, loading: true } //
|
|
202
|
-
});
|
|
203
|
-
|
|
204
|
-
await Promise.all([
|
|
205
|
-
new Promise((resolve) => setTimeout(resolve, delay)), //
|
|
206
|
-
executeDataPipeline(false) // Handle full resolution pipeline passing down false to bypass flashing double mutations
|
|
207
|
-
]);
|
|
208
|
-
|
|
209
|
-
const finishedState = selector.get() ?? sentinel;
|
|
210
|
-
await store.set({
|
|
211
|
-
[CACHE_KEY]: { ...finishedState, loading: false } //
|
|
212
|
-
});
|
|
213
|
-
},
|
|
214
|
-
|
|
215
|
-
subscribe: (listener) => {
|
|
216
|
-
return selector.subscribe((state) => { //
|
|
217
|
-
listener(state); //
|
|
218
|
-
});
|
|
219
|
-
},
|
|
220
|
-
|
|
221
|
-
invalidate: async () => {
|
|
222
|
-
await store.set({ [CACHE_KEY]: DELETE_SYMBOL }); //
|
|
223
|
-
pristineData = [];
|
|
224
|
-
},
|
|
225
|
-
};
|
|
226
|
-
}
|