@narrative.io/data-collaboration-sdk-ts 2.102.0 → 2.105.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/build/access-tokens/types.d.ts +1 -0
- package/build/access-tokens/types.js +1 -0
- package/build/agents/index.d.ts +7 -0
- package/build/agents/index.js +8 -0
- package/build/apps/index.d.ts +7 -0
- package/build/apps/index.js +9 -0
- package/build/collaboration-policy/core/filter-builder.js +7 -1
- package/build/collaboration-policy/core/jsonschema/json-schema-types.d.ts +45 -0
- package/build/collaboration-policy/core/jsonschema/json-schema-types.js +1 -0
- package/build/collaboration-policy/core/jsonschema/policy-branches.d.ts +49 -0
- package/build/collaboration-policy/core/jsonschema/policy-branches.js +1 -0
- package/build/collaboration-policy/core/jsonschema/policy-evaluator.d.ts +22 -0
- package/build/collaboration-policy/core/jsonschema/policy-evaluator.js +250 -0
- package/build/collaboration-policy/core/jsonschema/sql-builder.d.ts +79 -0
- package/build/collaboration-policy/core/jsonschema/sql-builder.js +306 -0
- package/build/collaboration-policy/core/types.d.ts +81 -10
- package/build/collaboration-policy/index.d.ts +5 -4
- package/build/collaboration-policy/index.js +4 -3
- package/build/collaboration-policy/useCollaborationPolicy.d.ts +13 -4
- package/build/collaboration-policy/useCollaborationPolicy.js +23 -1
- package/build/collaboration-policy/utils/path-helpers.d.ts +7 -3
- package/build/collaboration-policy/utils/path-helpers.js +6 -22
- package/build/datasets/index.d.ts +11 -2
- package/build/datasets/index.js +12 -0
- package/build/datasets/types.d.ts +25 -0
- package/package.json +2 -1
- package/build/collaboration-policy/core/filter-utils.d.ts +0 -6
- package/build/collaboration-policy/core/filter-utils.js +0 -49
- package/build/collaboration-policy/core/policy-traverser.d.ts +0 -4
- package/build/collaboration-policy/core/policy-traverser.js +0 -99
- package/build/collaboration-policy/core/policy-validator.d.ts +0 -11
- package/build/collaboration-policy/core/policy-validator.js +0 -185
- package/build/collaboration-policy/core/sql-builder.d.ts +0 -37
- package/build/collaboration-policy/core/sql-builder.js +0 -346
- package/build/collaboration-policy/scripts/generate-policy-schema.d.ts +0 -2
- package/build/collaboration-policy/scripts/generate-policy-schema.js +0 -4
- package/build/collaboration-policy/types/collaboration-policy.d.ts +0 -1370
- package/build/collaboration-policy/types/collaboration-policy.js +0 -274
- package/build/collaboration-policy/types/index.d.ts +0 -2
- package/build/collaboration-policy/types/index.js +0 -1
|
@@ -1,346 +0,0 @@
|
|
|
1
|
-
import { normalizePathValue } from "../utils/path-helpers";
|
|
2
|
-
import { buildSelectAlias, resolveAttributeExpression, } from "../utils/sql-helpers";
|
|
3
|
-
import { filterToSql } from "./filter-builder";
|
|
4
|
-
import { traversePolicy } from "./policy-traverser";
|
|
5
|
-
import { validatePolicyAgainstDataset } from "./policy-validator";
|
|
6
|
-
function buildAttributeIndexes(attributes) {
|
|
7
|
-
const byId = new Map();
|
|
8
|
-
const byName = new Map();
|
|
9
|
-
for (const attribute of attributes) {
|
|
10
|
-
byId.set(attribute.id, attribute);
|
|
11
|
-
byName.set(attribute.name, attribute);
|
|
12
|
-
}
|
|
13
|
-
return { byId, byName };
|
|
14
|
-
}
|
|
15
|
-
/**
|
|
16
|
-
* Converts an ISO 8601 duration string to milliseconds for comparison.
|
|
17
|
-
* This is an approximation for human-readable durations (months/years vary in length).
|
|
18
|
-
*
|
|
19
|
-
* @param duration - ISO 8601 duration string (e.g., "PT1H", "P1D", "P1M")
|
|
20
|
-
* @returns Duration in milliseconds
|
|
21
|
-
*/
|
|
22
|
-
function parseDurationToMilliseconds(duration) {
|
|
23
|
-
const regex = /^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)W)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/;
|
|
24
|
-
const matches = duration.match(regex);
|
|
25
|
-
if (!matches) {
|
|
26
|
-
throw new Error(`Invalid ISO 8601 duration format: ${duration}`);
|
|
27
|
-
}
|
|
28
|
-
const [, years, months, weeks, days, hours, minutes, seconds] = matches.map((m) => Number.parseInt(m || "0", 10));
|
|
29
|
-
// Approximate conversions (months = 30 days, years = 365 days)
|
|
30
|
-
const milliseconds = years * 365 * 24 * 60 * 60 * 1000 +
|
|
31
|
-
months * 30 * 24 * 60 * 60 * 1000 +
|
|
32
|
-
weeks * 7 * 24 * 60 * 60 * 1000 +
|
|
33
|
-
days * 24 * 60 * 60 * 1000 +
|
|
34
|
-
hours * 60 * 60 * 1000 +
|
|
35
|
-
minutes * 60 * 1000 +
|
|
36
|
-
seconds * 1000;
|
|
37
|
-
return milliseconds;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* Finds the smallest (most frequent) refresh schedule from a list of ISO 8601 duration strings.
|
|
41
|
-
* The smallest duration means the most frequent refresh rate.
|
|
42
|
-
*
|
|
43
|
-
* @param schedules - Array of ISO 8601 duration strings
|
|
44
|
-
* @returns The smallest duration in milliseconds, or 2592000000 (30 days/1 month) if no schedules provided
|
|
45
|
-
*/
|
|
46
|
-
function getSmallestRefreshSchedule(schedules) {
|
|
47
|
-
// Default to monthly (30 days in milliseconds)
|
|
48
|
-
const defaultMonthlyMs = 30 * 24 * 60 * 60 * 1000;
|
|
49
|
-
if (schedules.length === 0) {
|
|
50
|
-
return defaultMonthlyMs;
|
|
51
|
-
}
|
|
52
|
-
if (schedules.length === 1) {
|
|
53
|
-
try {
|
|
54
|
-
return parseDurationToMilliseconds(schedules[0]);
|
|
55
|
-
}
|
|
56
|
-
catch (error) {
|
|
57
|
-
console.warn(`Invalid refresh schedule: ${schedules[0]}, using default`, error);
|
|
58
|
-
return defaultMonthlyMs;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
// Find the schedule with the smallest duration (most frequent refresh)
|
|
62
|
-
let smallestValue = Number.MAX_SAFE_INTEGER;
|
|
63
|
-
for (const schedule of schedules) {
|
|
64
|
-
try {
|
|
65
|
-
const value = parseDurationToMilliseconds(schedule);
|
|
66
|
-
if (value < smallestValue) {
|
|
67
|
-
smallestValue = value;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
catch (error) {
|
|
71
|
-
// If parsing fails, skip this schedule
|
|
72
|
-
console.warn(`Skipping invalid refresh schedule: ${schedule}`, error);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
// If all schedules failed to parse, return default
|
|
76
|
-
return smallestValue === Number.MAX_SAFE_INTEGER
|
|
77
|
-
? defaultMonthlyMs
|
|
78
|
-
: smallestValue;
|
|
79
|
-
}
|
|
80
|
-
/**
|
|
81
|
-
* Builds structured WHERE clauses for a single branch node
|
|
82
|
-
*/
|
|
83
|
-
function buildBranchWhere(node, attributeByName, datasetName) {
|
|
84
|
-
if (!node || typeof node !== "object") {
|
|
85
|
-
return null;
|
|
86
|
-
}
|
|
87
|
-
// Handle field nodes (leaf nodes with attributes)
|
|
88
|
-
if ("field" in node) {
|
|
89
|
-
const extendedNode = node;
|
|
90
|
-
const field = extendedNode.field;
|
|
91
|
-
const attributeName = field.attribute_name;
|
|
92
|
-
const attribute = attributeByName.get(attributeName);
|
|
93
|
-
if (!attribute) {
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
const clauses = [];
|
|
97
|
-
// Add IS NOT NULL filters for additional_required_properties
|
|
98
|
-
if (field.additional_required_properties) {
|
|
99
|
-
for (const path of field.additional_required_properties) {
|
|
100
|
-
const pathStr = normalizePathValue(path);
|
|
101
|
-
const expression = `company_data."${datasetName}"."_rosetta_stone"."${attribute.name}"."${pathStr}"`;
|
|
102
|
-
clauses.push(`(${expression} IS NOT NULL)`);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
// Add filters
|
|
106
|
-
if (extendedNode.filters) {
|
|
107
|
-
for (const filter of extendedNode.filters) {
|
|
108
|
-
const sql = filterToSql(filter, attributeByName, datasetName);
|
|
109
|
-
clauses.push(sql);
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
// If only one clause, return as string
|
|
113
|
-
if (clauses.length === 0) {
|
|
114
|
-
return null;
|
|
115
|
-
}
|
|
116
|
-
if (clauses.length === 1) {
|
|
117
|
-
return clauses[0];
|
|
118
|
-
}
|
|
119
|
-
// Multiple clauses for a single field - AND them together
|
|
120
|
-
return {
|
|
121
|
-
operation: "AND",
|
|
122
|
-
fragments: clauses,
|
|
123
|
-
};
|
|
124
|
-
}
|
|
125
|
-
// Handle anyOf nodes
|
|
126
|
-
if ("anyOf" in node && Array.isArray(node.anyOf)) {
|
|
127
|
-
const satisfiedBranches = [];
|
|
128
|
-
for (const child of node.anyOf) {
|
|
129
|
-
if (isBranchSatisfied(child, attributeByName)) {
|
|
130
|
-
const branchWhere = buildBranchWhere(child, attributeByName, datasetName);
|
|
131
|
-
if (branchWhere) {
|
|
132
|
-
satisfiedBranches.push(branchWhere);
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
if (satisfiedBranches.length === 0) {
|
|
137
|
-
return null;
|
|
138
|
-
}
|
|
139
|
-
if (satisfiedBranches.length === 1) {
|
|
140
|
-
return satisfiedBranches[0];
|
|
141
|
-
}
|
|
142
|
-
// Multiple satisfied anyOf branches - OR them together
|
|
143
|
-
return {
|
|
144
|
-
operation: "OR",
|
|
145
|
-
fragments: satisfiedBranches,
|
|
146
|
-
};
|
|
147
|
-
}
|
|
148
|
-
// Handle allOf nodes
|
|
149
|
-
if ("allOf" in node && Array.isArray(node.allOf)) {
|
|
150
|
-
const allBranches = [];
|
|
151
|
-
for (const child of node.allOf) {
|
|
152
|
-
const branchWhere = buildBranchWhere(child, attributeByName, datasetName);
|
|
153
|
-
if (branchWhere) {
|
|
154
|
-
allBranches.push(branchWhere);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
if (allBranches.length === 0) {
|
|
158
|
-
return null;
|
|
159
|
-
}
|
|
160
|
-
if (allBranches.length === 1) {
|
|
161
|
-
return allBranches[0];
|
|
162
|
-
}
|
|
163
|
-
// Multiple allOf branches - AND them together
|
|
164
|
-
return {
|
|
165
|
-
operation: "AND",
|
|
166
|
-
fragments: allBranches,
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
return null;
|
|
170
|
-
}
|
|
171
|
-
/**
|
|
172
|
-
* Checks if a branch is satisfied (all required attributes exist)
|
|
173
|
-
*/
|
|
174
|
-
function isBranchSatisfied(node, attributeByName) {
|
|
175
|
-
if (!node || typeof node !== "object") {
|
|
176
|
-
return true;
|
|
177
|
-
}
|
|
178
|
-
if ("field" in node) {
|
|
179
|
-
const field = node
|
|
180
|
-
.field;
|
|
181
|
-
if (field.type === "attribute") {
|
|
182
|
-
const attributeName = field.attribute_name;
|
|
183
|
-
return attributeByName.has(attributeName);
|
|
184
|
-
}
|
|
185
|
-
return true;
|
|
186
|
-
}
|
|
187
|
-
if ("anyOf" in node && Array.isArray(node.anyOf)) {
|
|
188
|
-
return node.anyOf.some((child) => isBranchSatisfied(child, attributeByName));
|
|
189
|
-
}
|
|
190
|
-
if ("allOf" in node && Array.isArray(node.allOf)) {
|
|
191
|
-
return node.allOf.every((child) => isBranchSatisfied(child, attributeByName));
|
|
192
|
-
}
|
|
193
|
-
return true;
|
|
194
|
-
}
|
|
195
|
-
function buildPolicySqlUsingIndexes(policies, attributeByName, datasetName) {
|
|
196
|
-
const globalRequiredAttributePaths = new Map();
|
|
197
|
-
const policyWhereGroups = [];
|
|
198
|
-
for (const policy of policies) {
|
|
199
|
-
const policyRequiredAttributePaths = new Map();
|
|
200
|
-
const policyWhereClausesSet = new Set();
|
|
201
|
-
// Still use traversePolicy to collect required attributes for SELECT
|
|
202
|
-
traversePolicy(policy, attributeByName, policyRequiredAttributePaths, policyWhereClausesSet, datasetName);
|
|
203
|
-
// Build structured WHERE for this policy
|
|
204
|
-
const policyWhere = buildBranchWhere(policy.policy.definition.structure, attributeByName, datasetName);
|
|
205
|
-
if (policyWhere) {
|
|
206
|
-
policyWhereGroups.push(policyWhere);
|
|
207
|
-
}
|
|
208
|
-
for (const [attributeName, paths] of policyRequiredAttributePaths) {
|
|
209
|
-
const globalPaths = globalRequiredAttributePaths.get(attributeName) ?? new Set();
|
|
210
|
-
for (const path of paths) {
|
|
211
|
-
globalPaths.add(path);
|
|
212
|
-
}
|
|
213
|
-
globalRequiredAttributePaths.set(attributeName, globalPaths);
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
const selectClauses = new Set();
|
|
217
|
-
for (const [attributeName, _paths] of globalRequiredAttributePaths) {
|
|
218
|
-
const attribute = attributeByName.get(attributeName);
|
|
219
|
-
if (!attribute) {
|
|
220
|
-
continue;
|
|
221
|
-
}
|
|
222
|
-
// Always select the root attribute (without path)
|
|
223
|
-
const expression = resolveAttributeExpression(attribute, "", // Empty path for root attribute
|
|
224
|
-
datasetName);
|
|
225
|
-
const alias = buildSelectAlias(attributeName, "");
|
|
226
|
-
selectClauses.add(`${expression} AS ${alias}`);
|
|
227
|
-
}
|
|
228
|
-
// Build the root AND structure
|
|
229
|
-
// Root level is always AND (combines policies)
|
|
230
|
-
// Each policy's branches are already OR'd/AND'd by buildBranchWhere
|
|
231
|
-
let where;
|
|
232
|
-
if (policyWhereGroups.length === 0) {
|
|
233
|
-
where = [];
|
|
234
|
-
}
|
|
235
|
-
else {
|
|
236
|
-
// Wrap all policies in root AND
|
|
237
|
-
// Each policy in policyWhereGroups is already a complete policy structure
|
|
238
|
-
where = [
|
|
239
|
-
{
|
|
240
|
-
fragments: policyWhereGroups,
|
|
241
|
-
operation: "AND",
|
|
242
|
-
},
|
|
243
|
-
];
|
|
244
|
-
}
|
|
245
|
-
return {
|
|
246
|
-
select: Array.from(selectClauses),
|
|
247
|
-
where,
|
|
248
|
-
};
|
|
249
|
-
}
|
|
250
|
-
export function buildPolicySqlWithValidation(dataset, policies, attributes) {
|
|
251
|
-
const attributeIndexes = buildAttributeIndexes(attributes);
|
|
252
|
-
const validPolicies = [];
|
|
253
|
-
const skippedPolicies = [];
|
|
254
|
-
const warnings = [];
|
|
255
|
-
// Validate each policy against the dataset
|
|
256
|
-
for (const policy of policies) {
|
|
257
|
-
const validation = validatePolicyAgainstDataset(policy, dataset, attributes);
|
|
258
|
-
if (validation.isValid) {
|
|
259
|
-
validPolicies.push(policy);
|
|
260
|
-
}
|
|
261
|
-
else {
|
|
262
|
-
skippedPolicies.push({
|
|
263
|
-
name: policy.display_name || policy.name,
|
|
264
|
-
reason: "Missing required attributes or mappings",
|
|
265
|
-
details: validation.errors,
|
|
266
|
-
});
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
// Check if we have at least one valid policy
|
|
270
|
-
if (validPolicies.length === 0) {
|
|
271
|
-
const errorMessage = policies.length === 0
|
|
272
|
-
? "No policies provided"
|
|
273
|
-
: `No policies are compatible with dataset "${dataset.display_name || dataset.name}".`;
|
|
274
|
-
const details = skippedPolicies.length > 0
|
|
275
|
-
? [`Skipped policies: ${skippedPolicies.map((p) => p.name).join(", ")}`]
|
|
276
|
-
: [];
|
|
277
|
-
throw new Error(`${errorMessage}\n${details.join("\n")}`);
|
|
278
|
-
}
|
|
279
|
-
if (skippedPolicies.length > 0) {
|
|
280
|
-
warnings.push(`Skipped ${skippedPolicies.length} incompatible policies: ${skippedPolicies.map((p) => p.name).join(", ")}`);
|
|
281
|
-
}
|
|
282
|
-
// Build SQL for valid policies
|
|
283
|
-
const sqlFragments = buildPolicySqlUsingIndexes(validPolicies, attributeIndexes.byName, dataset.name);
|
|
284
|
-
// Calculate the smallest refresh schedule from applied policies
|
|
285
|
-
const refreshSchedules = validPolicies
|
|
286
|
-
.map((p) => p.policy.metadata?.refresh_schedule?.max)
|
|
287
|
-
.filter((schedule) => schedule !== undefined);
|
|
288
|
-
const refreshSchedule = getSmallestRefreshSchedule(refreshSchedules);
|
|
289
|
-
return {
|
|
290
|
-
...sqlFragments,
|
|
291
|
-
appliedPolicies: validPolicies.map((p) => p.name),
|
|
292
|
-
skippedPolicies,
|
|
293
|
-
warnings,
|
|
294
|
-
refresh_schedule: refreshSchedule,
|
|
295
|
-
};
|
|
296
|
-
}
|
|
297
|
-
/**
|
|
298
|
-
* Categorizes collaboration policies into matching and non-matching groups based on dataset compatibility.
|
|
299
|
-
*
|
|
300
|
-
* This function validates each policy against the provided dataset and attributes to determine
|
|
301
|
-
* whether the policy can be successfully applied. Policies are categorized as matching if they
|
|
302
|
-
* have all required attributes available, or non-matching if they're missing required attributes
|
|
303
|
-
* or mappings.
|
|
304
|
-
*
|
|
305
|
-
* @param policies - Array of collaboration policies to categorize
|
|
306
|
-
* @param dataset - The dataset to validate policies against
|
|
307
|
-
* @param attributes - Available attributes for validation
|
|
308
|
-
* @returns Object containing matching policies, non-matching policies, and detailed skip information
|
|
309
|
-
*
|
|
310
|
-
* @example
|
|
311
|
-
* ```typescript
|
|
312
|
-
* const { matching, nonMatching, skippedDetails } = categorizePolicies(
|
|
313
|
-
* allPolicies,
|
|
314
|
-
* myDataset,
|
|
315
|
-
* availableAttributes
|
|
316
|
-
* );
|
|
317
|
-
*
|
|
318
|
-
* console.log(`${matching.length} policies can be applied`);
|
|
319
|
-
* console.log(`${nonMatching.length} policies were skipped`);
|
|
320
|
-
* skippedDetails.forEach(skip => console.log(`Skipped ${skip.name}: ${skip.reason}`));
|
|
321
|
-
* ```
|
|
322
|
-
*/
|
|
323
|
-
export function categorizePolicies(policies, dataset, attributes) {
|
|
324
|
-
const matching = [];
|
|
325
|
-
const nonMatching = [];
|
|
326
|
-
const skippedDetails = [];
|
|
327
|
-
for (const policy of policies) {
|
|
328
|
-
const validation = validatePolicyAgainstDataset(policy, dataset, attributes);
|
|
329
|
-
if (validation.isValid) {
|
|
330
|
-
matching.push(policy);
|
|
331
|
-
}
|
|
332
|
-
else {
|
|
333
|
-
nonMatching.push(policy);
|
|
334
|
-
skippedDetails.push({
|
|
335
|
-
name: policy.display_name || policy.name,
|
|
336
|
-
reason: "Missing required attributes or mappings",
|
|
337
|
-
details: validation.errors,
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
return { matching, nonMatching, skippedDetails };
|
|
342
|
-
}
|
|
343
|
-
export function buildPolicySql(dataset, policies, attributes) {
|
|
344
|
-
const attributeIndexes = buildAttributeIndexes(attributes);
|
|
345
|
-
return buildPolicySqlUsingIndexes(policies, attributeIndexes.byName, dataset.name);
|
|
346
|
-
}
|