Gesamte Mono-Repo des Baukastens (alle apps/clients, examples/tests und packages)
dh_heyart
vor 5 Std. 96438053d50d33b24c946cd290d57356b4a80b73
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
import {
    listCustomerNumbers as listCustomerNumbersInternal,
    readCatalog as readCatalogInternal,
    selectableFor,
} from "@dh-software/baukasten-permissions";
import {
    readConfiguration as readConfigurationInternal,
    readConfigurations as readConfigurationsInternal,
    resolveConfigurationPath as resolveConfigurationPathInternal,
} from "@dh-software/baukasten-config-reader";
import type {
    ConfigurationEntry,
    ConfigurationReference,
    CustomerNumber,
    JsonValue,
    StoreMembershipResolver,
} from "@dh-software/baukasten-types";
 
/** Options for {@link createBaukasten}. */
export interface BaukastenOptions {
    /** Absolute path to the baukasten "configurations" directory. */
    configurationsDir: string;
    /**
     * Resolves whether a requesting customer is part of an owner's StoreTree
     * (furncloudcredentials). Supplied by the host (furnplan_web) so the library
     * never touches the database. May be sync or async. When omitted, "stores"
     * grants never match foreign customers.
     */
    isStoreMember?: StoreMembershipResolver;
}
 
/**
 * The stable baukasten API surface. Consumers (furnplan_web) depend only on this
 * contract; the internal package layout and helper signatures may change freely
 * as long as these method shapes stay the same.
 */
export interface Baukasten {
    /** All customer-number folders present under the configurations directory. */
    listCustomerNumbers(): Promise<CustomerNumber[]>;
    /** The full catalog across all customers (display-names + resolved permissions). */
    getCatalog(): Promise<ConfigurationEntry[]>;
    /** The catalog filtered to what `requestingCustomerNumber` may select (for the AUC). */
    getSelectableConfigurations(
        requestingCustomerNumber: CustomerNumber,
    ): Promise<ConfigurationEntry[]>;
    /** Resolves a reference to its absolute file path (throws if missing). */
    resolveConfigurationPath(reference: ConfigurationReference): Promise<string>;
    /** Reads & parses a single referenced configuration. */
    readConfiguration(reference: ConfigurationReference): Promise<JsonValue>;
    /** Reads & parses an ordered list of references, preserving merge order. */
    readConfigurations(references: ConfigurationReference[]): Promise<JsonValue[]>;
}
 
/** Creates a {@link Baukasten} bound to one configurations directory + store resolver. */
export function createBaukasten(options: BaukastenOptions): Baukasten {
    const { configurationsDir, isStoreMember } = options;
 
    return {
        listCustomerNumbers() {
            return listCustomerNumbersInternal(configurationsDir);
        },
        getCatalog() {
            return readCatalogInternal(configurationsDir);
        },
        async getSelectableConfigurations(requestingCustomerNumber) {
            const catalog = await readCatalogInternal(configurationsDir);
 
            // Pre-resolve the (possibly async) StoreTree membership only for the
            // owners whose visibility actually depends on it, then filter synchronously.
            const storeOwners = new Set(
                catalog
                    .filter(
                        (entry) =>
                            entry.permission.blanket === "stores" &&
                            entry.permission.owner !== requestingCustomerNumber &&
                            !entry.permission.consumers.includes(requestingCustomerNumber),
                    )
                    .map((entry) => entry.permission.owner),
            );
 
            const memberOf = new Set<CustomerNumber>();
            for (const owner of storeOwners) {
                if (isStoreMember && (await isStoreMember(owner, requestingCustomerNumber))) {
                    memberOf.add(owner);
                }
            }
 
            return selectableFor(catalog, requestingCustomerNumber, {
                isStoreMember: (owner) => memberOf.has(owner),
            });
        },
        resolveConfigurationPath(reference) {
            return resolveConfigurationPathInternal(configurationsDir, reference);
        },
        readConfiguration(reference) {
            return readConfigurationInternal(configurationsDir, reference);
        },
        readConfigurations(references) {
            return readConfigurationsInternal(configurationsDir, references);
        },
    };
}