@narrative.io/data-collaboration-sdk-ts 2.64.1 → 2.66.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/README.md +137 -1
- package/build/collaboration-policy/core/filter-builder.d.ts +3 -0
- package/build/collaboration-policy/core/filter-builder.js +90 -0
- package/build/collaboration-policy/core/filter-utils.d.ts +6 -0
- package/build/collaboration-policy/core/filter-utils.js +49 -0
- package/build/collaboration-policy/core/policy-traverser.d.ts +4 -0
- package/build/collaboration-policy/core/policy-traverser.js +100 -0
- package/build/collaboration-policy/core/policy-validator.d.ts +11 -0
- package/build/collaboration-policy/core/policy-validator.js +148 -0
- package/build/collaboration-policy/core/sql-builder.d.ts +37 -0
- package/build/collaboration-policy/core/sql-builder.js +173 -0
- package/build/collaboration-policy/core/types.d.ts +24 -0
- package/build/collaboration-policy/core/types.js +1 -0
- package/build/collaboration-policy/index.d.ts +4 -0
- package/build/collaboration-policy/index.js +3 -0
- package/build/collaboration-policy/scripts/generate-policy-schema.d.ts +2 -0
- package/build/collaboration-policy/scripts/generate-policy-schema.js +4 -0
- package/build/collaboration-policy/types/collaboration-policy.d.ts +697 -0
- package/build/collaboration-policy/types/collaboration-policy.js +262 -0
- package/build/collaboration-policy/types/index.d.ts +2 -0
- package/build/collaboration-policy/types/index.js +1 -0
- package/build/collaboration-policy/useCollaborationPolicy.d.ts +8 -0
- package/build/collaboration-policy/useCollaborationPolicy.js +14 -0
- package/build/collaboration-policy/utils/path-helpers.d.ts +4 -0
- package/build/collaboration-policy/utils/path-helpers.js +39 -0
- package/build/collaboration-policy/utils/sql-helpers.d.ts +5 -0
- package/build/collaboration-policy/utils/sql-helpers.js +65 -0
- package/build/index.d.ts +1 -0
- package/build/index.js +1 -0
- package/build/jobs/types.d.ts +7 -0
- package/build/nql/AstParser.js +5 -4
- package/build/nql/NqlBuilder.js +1 -1
- package/build/nql/types.d.ts +13 -0
- package/package.json +16 -12
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { buildSelectAlias, resolveAttributeExpression, } from "../utils/sql-helpers";
|
|
2
|
+
import { traversePolicy } from "./policy-traverser";
|
|
3
|
+
import { validatePolicyAgainstDataset } from "./policy-validator";
|
|
4
|
+
function buildAttributeIndexes(attributes) {
|
|
5
|
+
const byId = new Map();
|
|
6
|
+
const byName = new Map();
|
|
7
|
+
for (const attribute of attributes) {
|
|
8
|
+
byId.set(attribute.id, attribute);
|
|
9
|
+
byName.set(attribute.name, attribute);
|
|
10
|
+
}
|
|
11
|
+
return { byId, byName };
|
|
12
|
+
}
|
|
13
|
+
function getSmallestRefreshSchedule(schedules) {
|
|
14
|
+
if (schedules.length === 0) {
|
|
15
|
+
return "@monthly";
|
|
16
|
+
}
|
|
17
|
+
// Define the hierarchy for refresh schedules (from smallest to largest)
|
|
18
|
+
const scheduleHierarchy = {
|
|
19
|
+
"@once": 0,
|
|
20
|
+
"@hourly": 1,
|
|
21
|
+
"@daily": 2,
|
|
22
|
+
"@weekly": 3,
|
|
23
|
+
"@monthly": 4,
|
|
24
|
+
};
|
|
25
|
+
// Find the schedule with the smallest value (most frequent)
|
|
26
|
+
let smallestSchedule = "@monthly";
|
|
27
|
+
let smallestValue = scheduleHierarchy["@monthly"];
|
|
28
|
+
for (const schedule of schedules) {
|
|
29
|
+
if (schedule in scheduleHierarchy) {
|
|
30
|
+
const value = scheduleHierarchy[schedule];
|
|
31
|
+
if (value < smallestValue) {
|
|
32
|
+
smallestValue = value;
|
|
33
|
+
smallestSchedule = schedule;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
// If it's a custom cron expression, treat it as the most frequent (smallest)
|
|
38
|
+
// since we can't easily compare cron expressions
|
|
39
|
+
return schedule;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return smallestSchedule;
|
|
43
|
+
}
|
|
44
|
+
function buildPolicySqlUsingIndexes(policies, attributeByName, datasetName) {
|
|
45
|
+
const globalRequiredAttributePaths = new Map();
|
|
46
|
+
const policyWhereClauses = [];
|
|
47
|
+
for (const policy of policies) {
|
|
48
|
+
const policyRequiredAttributePaths = new Map();
|
|
49
|
+
const policyWhereClausesSet = new Set();
|
|
50
|
+
traversePolicy(policy, attributeByName, policyRequiredAttributePaths, policyWhereClausesSet, datasetName);
|
|
51
|
+
policyWhereClauses.push(Array.from(policyWhereClausesSet));
|
|
52
|
+
for (const [attributeName, paths] of policyRequiredAttributePaths) {
|
|
53
|
+
const globalPaths = globalRequiredAttributePaths.get(attributeName) ?? new Set();
|
|
54
|
+
for (const path of paths) {
|
|
55
|
+
globalPaths.add(path);
|
|
56
|
+
}
|
|
57
|
+
globalRequiredAttributePaths.set(attributeName, globalPaths);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const selectClauses = new Set();
|
|
61
|
+
for (const [attributeName, _paths] of globalRequiredAttributePaths) {
|
|
62
|
+
const attribute = attributeByName.get(attributeName);
|
|
63
|
+
if (!attribute) {
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
// Always select the root attribute (without path)
|
|
67
|
+
const expression = resolveAttributeExpression(attribute, "", // Empty path for root attribute
|
|
68
|
+
datasetName);
|
|
69
|
+
const alias = buildSelectAlias(attributeName, "");
|
|
70
|
+
selectClauses.add(`${expression} AS ${alias}`);
|
|
71
|
+
}
|
|
72
|
+
return {
|
|
73
|
+
select: Array.from(selectClauses),
|
|
74
|
+
where: policyWhereClauses,
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
export function buildPolicySqlWithValidation(dataset, policies, attributes) {
|
|
78
|
+
const attributeIndexes = buildAttributeIndexes(attributes);
|
|
79
|
+
const validPolicies = [];
|
|
80
|
+
const skippedPolicies = [];
|
|
81
|
+
const warnings = [];
|
|
82
|
+
// Validate each policy against the dataset
|
|
83
|
+
for (const policy of policies) {
|
|
84
|
+
const validation = validatePolicyAgainstDataset(policy, dataset, attributes);
|
|
85
|
+
if (validation.isValid) {
|
|
86
|
+
validPolicies.push(policy);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
skippedPolicies.push({
|
|
90
|
+
name: policy.display_name || policy.name,
|
|
91
|
+
reason: "Missing required attributes or mappings",
|
|
92
|
+
details: validation.errors,
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Check if we have at least one valid policy
|
|
97
|
+
if (validPolicies.length === 0) {
|
|
98
|
+
const errorMessage = policies.length === 0
|
|
99
|
+
? "No policies provided"
|
|
100
|
+
: `No policies are compatible with dataset "${dataset.display_name || dataset.name}".`;
|
|
101
|
+
const details = skippedPolicies.length > 0
|
|
102
|
+
? [`Skipped policies: ${skippedPolicies.map((p) => p.name).join(", ")}`]
|
|
103
|
+
: [];
|
|
104
|
+
throw new Error(`${errorMessage}\n${details.join("\n")}`);
|
|
105
|
+
}
|
|
106
|
+
if (skippedPolicies.length > 0) {
|
|
107
|
+
warnings.push(`Skipped ${skippedPolicies.length} incompatible policies: ${skippedPolicies.map((p) => p.name).join(", ")}`);
|
|
108
|
+
}
|
|
109
|
+
// Build SQL for valid policies
|
|
110
|
+
const sqlFragments = buildPolicySqlUsingIndexes(validPolicies, attributeIndexes.byName, dataset.name);
|
|
111
|
+
// Calculate the smallest refresh schedule from applied policies
|
|
112
|
+
const refreshSchedules = validPolicies
|
|
113
|
+
.map((p) => p.policy.metadata?.refresh_schedule?.max)
|
|
114
|
+
.filter((schedule) => schedule !== undefined);
|
|
115
|
+
const refreshSchedule = getSmallestRefreshSchedule(refreshSchedules);
|
|
116
|
+
return {
|
|
117
|
+
...sqlFragments,
|
|
118
|
+
appliedPolicies: validPolicies.map((p) => p.name),
|
|
119
|
+
skippedPolicies,
|
|
120
|
+
warnings,
|
|
121
|
+
refresh_schedule: refreshSchedule,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Categorizes collaboration policies into matching and non-matching groups based on dataset compatibility.
|
|
126
|
+
*
|
|
127
|
+
* This function validates each policy against the provided dataset and attributes to determine
|
|
128
|
+
* whether the policy can be successfully applied. Policies are categorized as matching if they
|
|
129
|
+
* have all required attributes available, or non-matching if they're missing required attributes
|
|
130
|
+
* or mappings.
|
|
131
|
+
*
|
|
132
|
+
* @param policies - Array of collaboration policies to categorize
|
|
133
|
+
* @param dataset - The dataset to validate policies against
|
|
134
|
+
* @param attributes - Available attributes for validation
|
|
135
|
+
* @returns Object containing matching policies, non-matching policies, and detailed skip information
|
|
136
|
+
*
|
|
137
|
+
* @example
|
|
138
|
+
* ```typescript
|
|
139
|
+
* const { matching, nonMatching, skippedDetails } = categorizePolicies(
|
|
140
|
+
* allPolicies,
|
|
141
|
+
* myDataset,
|
|
142
|
+
* availableAttributes
|
|
143
|
+
* );
|
|
144
|
+
*
|
|
145
|
+
* console.log(`${matching.length} policies can be applied`);
|
|
146
|
+
* console.log(`${nonMatching.length} policies were skipped`);
|
|
147
|
+
* skippedDetails.forEach(skip => console.log(`Skipped ${skip.name}: ${skip.reason}`));
|
|
148
|
+
* ```
|
|
149
|
+
*/
|
|
150
|
+
export function categorizePolicies(policies, dataset, attributes) {
|
|
151
|
+
const matching = [];
|
|
152
|
+
const nonMatching = [];
|
|
153
|
+
const skippedDetails = [];
|
|
154
|
+
for (const policy of policies) {
|
|
155
|
+
const validation = validatePolicyAgainstDataset(policy, dataset, attributes);
|
|
156
|
+
if (validation.isValid) {
|
|
157
|
+
matching.push(policy);
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
nonMatching.push(policy);
|
|
161
|
+
skippedDetails.push({
|
|
162
|
+
name: policy.display_name || policy.name,
|
|
163
|
+
reason: "Missing required attributes or mappings",
|
|
164
|
+
details: validation.errors,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return { matching, nonMatching, skippedDetails };
|
|
169
|
+
}
|
|
170
|
+
export function buildPolicySql(dataset, policies, attributes) {
|
|
171
|
+
const attributeIndexes = buildAttributeIndexes(attributes);
|
|
172
|
+
return buildPolicySqlUsingIndexes(policies, attributeIndexes.byName, dataset.name);
|
|
173
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { CollaborationPolicyType } from "../types";
|
|
2
|
+
type PolicyDefinition = CollaborationPolicyType["policy"]["definition"];
|
|
3
|
+
export type StructureNode = PolicyDefinition["structure"];
|
|
4
|
+
export type ExtendedAttributeNode = Extract<StructureNode, {
|
|
5
|
+
field: unknown;
|
|
6
|
+
}>;
|
|
7
|
+
export type PolicyFilter = NonNullable<PolicyDefinition["filters"]>[number];
|
|
8
|
+
export type PathValue = NonNullable<ExtendedAttributeNode["field"]["additional_required_properties"]>[number];
|
|
9
|
+
export type AttributePathIndex = Map<string, Set<string>>;
|
|
10
|
+
export interface PolicySqlFragments {
|
|
11
|
+
select: string[];
|
|
12
|
+
where: string[][];
|
|
13
|
+
}
|
|
14
|
+
export interface PolicyMatchResult extends PolicySqlFragments {
|
|
15
|
+
appliedPolicies: string[];
|
|
16
|
+
skippedPolicies: Array<{
|
|
17
|
+
name: string;
|
|
18
|
+
reason: string;
|
|
19
|
+
details: string[];
|
|
20
|
+
}>;
|
|
21
|
+
warnings: string[];
|
|
22
|
+
refresh_schedule: string;
|
|
23
|
+
}
|
|
24
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { buildPolicySql, buildPolicySqlWithValidation, categorizePolicies, } from "./core/sql-builder";
|
|
2
|
+
export type { AttributePathIndex, ExtendedAttributeNode, PathValue, PolicyFilter, PolicyMatchResult, PolicySqlFragments, StructureNode, } from "./core/types";
|
|
3
|
+
export type { CollaborationPolicyInput, CollaborationPolicyType, } from "./types";
|
|
4
|
+
export { buildCollaborationPolicyJsonSchema, CollaborationPolicy, } from "./types";
|