@narrative.io/data-collaboration-sdk-ts 2.98.1-beta.0 → 2.99.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/collaboration-policy/types/collaboration-policy.d.ts +20 -20
- package/package.json +2 -2
- package/build/collaboration-policy/core/jsonschema/json-schema-types.d.ts +0 -45
- package/build/collaboration-policy/core/jsonschema/json-schema-types.js +0 -1
- package/build/collaboration-policy/core/jsonschema/policy-branches.d.ts +0 -49
- package/build/collaboration-policy/core/jsonschema/policy-branches.js +0 -1
- package/build/collaboration-policy/core/jsonschema/policy-evaluator.d.ts +0 -22
- package/build/collaboration-policy/core/jsonschema/policy-evaluator.js +0 -260
- package/build/collaboration-policy/core/jsonschema/sql-builder.d.ts +0 -79
- package/build/collaboration-policy/core/jsonschema/sql-builder.js +0 -306
- package/build/collaboration-policy/core/jsonschema/sql-poc.d.ts +0 -79
- package/build/collaboration-policy/core/jsonschema/sql-poc.js +0 -305
- package/build/nql/SubstraitParser.d.ts +0 -771
- package/build/nql/SubstraitParser.js +0 -797
|
@@ -1,305 +0,0 @@
|
|
|
1
|
-
import { buildSelectAlias, resolveAttributeExpression, } from "../../utils/sql-helpers";
|
|
2
|
-
import { filterToSql } from "../filter-builder";
|
|
3
|
-
import { evaluateJsonSchemaPolicies } from "./policy-evaluator";
|
|
4
|
-
/**
|
|
5
|
-
* Convert an ISO 8601 duration string (e.g. "P1D", "PT1H") into milliseconds.
|
|
6
|
-
* This is an approximation (months and years are treated as 30 and 365 days respectively).
|
|
7
|
-
*
|
|
8
|
-
* @param duration - The ISO 8601 duration string.
|
|
9
|
-
* @returns The approximate duration in milliseconds.
|
|
10
|
-
* @throws If the duration string does not match the ISO 8601 duration format.
|
|
11
|
-
*/
|
|
12
|
-
function parseDurationToMilliseconds(duration) {
|
|
13
|
-
const regex = /^P(?:(\d+)Y)?(?:(\d+)M)?(?:(\d+)W)?(?:(\d+)D)?(?:T(?:(\d+)H)?(?:(\d+)M)?(?:(\d+)S)?)?$/;
|
|
14
|
-
const matches = duration.match(regex);
|
|
15
|
-
if (!matches) {
|
|
16
|
-
throw new Error(`Invalid ISO 8601 duration format: ${duration}`);
|
|
17
|
-
}
|
|
18
|
-
const [, years, months, weeks, days, hours, minutes, seconds] = matches.map((m) => Number.parseInt(m || "0", 10));
|
|
19
|
-
const milliseconds = years * 365 * 24 * 60 * 60 * 1000 +
|
|
20
|
-
months * 30 * 24 * 60 * 60 * 1000 +
|
|
21
|
-
weeks * 7 * 24 * 60 * 60 * 1000 +
|
|
22
|
-
days * 24 * 60 * 60 * 1000 +
|
|
23
|
-
hours * 60 * 60 * 1000 +
|
|
24
|
-
minutes * 60 * 1000 +
|
|
25
|
-
seconds * 1000;
|
|
26
|
-
return milliseconds;
|
|
27
|
-
}
|
|
28
|
-
/**
|
|
29
|
-
* Find the "smallest" (most frequent) ISO 8601 duration within a list.
|
|
30
|
-
*
|
|
31
|
-
* @param schedules - Array of ISO 8601 duration strings.
|
|
32
|
-
* @returns The smallest duration as an ISO 8601 string, or "P1M" if none are valid.
|
|
33
|
-
*/
|
|
34
|
-
function getSmallestRefreshSchedule(schedules) {
|
|
35
|
-
const defaultMonthly = "P1M";
|
|
36
|
-
if (schedules.length === 0)
|
|
37
|
-
return defaultMonthly;
|
|
38
|
-
if (schedules.length === 1) {
|
|
39
|
-
try {
|
|
40
|
-
parseDurationToMilliseconds(schedules[0]);
|
|
41
|
-
return schedules[0];
|
|
42
|
-
}
|
|
43
|
-
catch {
|
|
44
|
-
return defaultMonthly;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
let smallestValue = Number.MAX_SAFE_INTEGER;
|
|
48
|
-
let smallestSchedule = null;
|
|
49
|
-
for (const schedule of schedules) {
|
|
50
|
-
try {
|
|
51
|
-
const value = parseDurationToMilliseconds(schedule);
|
|
52
|
-
if (value < smallestValue) {
|
|
53
|
-
smallestValue = value;
|
|
54
|
-
smallestSchedule = schedule;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
catch {
|
|
58
|
-
// skip invalid schedule
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
return smallestSchedule ?? defaultMonthly;
|
|
62
|
-
}
|
|
63
|
-
/**
|
|
64
|
-
* Build SQL fragments (SELECT + WHERE) from branch matches across all applied policies.
|
|
65
|
-
*
|
|
66
|
-
* SELECT: one entry per unique required attribute (root level, no subfield paths):
|
|
67
|
-
* company_data."<dataset>"."_rosetta_stone"."<attr>" AS "<attr>"
|
|
68
|
-
*
|
|
69
|
-
* WHERE: structured as root AND → per-policy structure → OR of branches.
|
|
70
|
-
* Each branch contains IS NOT NULL clauses (from additionalRequiredProperties)
|
|
71
|
-
* and filter SQL (from filters), ANDed together.
|
|
72
|
-
*
|
|
73
|
-
* @param branchMatches - Per-policy branch match results from evaluateJsonSchemaPolicies.
|
|
74
|
-
* @param attributeByName - Map of attribute name → Attribute for SQL expression building.
|
|
75
|
-
* @param datasetName - Dataset name used in SQL column paths.
|
|
76
|
-
* @returns PolicySqlFragments with select and where arrays.
|
|
77
|
-
*/
|
|
78
|
-
function buildSqlFromBranchMatches(branchMatches, attributeByName, datasetName) {
|
|
79
|
-
// --- SELECT ---
|
|
80
|
-
// Collect all unique required attributes across all policies and branches
|
|
81
|
-
const globalRequiredAttributes = new Set();
|
|
82
|
-
for (const policyMatch of branchMatches) {
|
|
83
|
-
for (const branch of policyMatch.branches) {
|
|
84
|
-
for (const attrName of branch.requiredAttributes) {
|
|
85
|
-
globalRequiredAttributes.add(attrName);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
const selectClauses = new Set();
|
|
90
|
-
for (const attrName of globalRequiredAttributes) {
|
|
91
|
-
const attribute = attributeByName.get(attrName);
|
|
92
|
-
if (!attribute)
|
|
93
|
-
continue;
|
|
94
|
-
const expression = resolveAttributeExpression(attribute, "", datasetName);
|
|
95
|
-
const alias = buildSelectAlias(attrName, "");
|
|
96
|
-
selectClauses.add(`${expression} AS ${alias}`);
|
|
97
|
-
}
|
|
98
|
-
// --- WHERE ---
|
|
99
|
-
const policyWhereGroups = [];
|
|
100
|
-
for (const policyMatch of branchMatches) {
|
|
101
|
-
const branchWheres = [];
|
|
102
|
-
for (const branch of policyMatch.branches) {
|
|
103
|
-
const branchClauses = [];
|
|
104
|
-
// 1. IS NOT NULL clauses derived from $ref/$defs resolution
|
|
105
|
-
for (const req of branch.additionalRequiredProperties) {
|
|
106
|
-
const attribute = attributeByName.get(req.attributeName);
|
|
107
|
-
if (!attribute)
|
|
108
|
-
continue;
|
|
109
|
-
for (const path of req.paths) {
|
|
110
|
-
const expression = resolveAttributeExpression(attribute, path, datasetName);
|
|
111
|
-
branchClauses.push(`(${expression} IS NOT NULL)`);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
// 2. Filter SQL
|
|
115
|
-
for (const filter of branch.filters) {
|
|
116
|
-
const sql = filterToSql(filter, attributeByName, datasetName);
|
|
117
|
-
branchClauses.push(sql);
|
|
118
|
-
}
|
|
119
|
-
if (branchClauses.length === 0) {
|
|
120
|
-
continue; // branch has no WHERE contribution
|
|
121
|
-
}
|
|
122
|
-
if (branchClauses.length === 1) {
|
|
123
|
-
branchWheres.push(branchClauses[0]);
|
|
124
|
-
}
|
|
125
|
-
else {
|
|
126
|
-
branchWheres.push({
|
|
127
|
-
operation: "AND",
|
|
128
|
-
fragments: branchClauses,
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
if (branchWheres.length === 0) {
|
|
133
|
-
continue; // policy has no WHERE contribution
|
|
134
|
-
}
|
|
135
|
-
if (branchWheres.length === 1) {
|
|
136
|
-
policyWhereGroups.push(branchWheres[0]);
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
// Multiple branches → OR them (anyOf semantics)
|
|
140
|
-
policyWhereGroups.push({
|
|
141
|
-
operation: "OR",
|
|
142
|
-
fragments: branchWheres,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
let where;
|
|
147
|
-
if (policyWhereGroups.length === 0) {
|
|
148
|
-
where = [];
|
|
149
|
-
}
|
|
150
|
-
else {
|
|
151
|
-
// Root AND wrapping all policies
|
|
152
|
-
where = [
|
|
153
|
-
{
|
|
154
|
-
fragments: policyWhereGroups,
|
|
155
|
-
operation: "AND",
|
|
156
|
-
},
|
|
157
|
-
];
|
|
158
|
-
}
|
|
159
|
-
return {
|
|
160
|
-
select: Array.from(selectClauses),
|
|
161
|
-
where,
|
|
162
|
-
};
|
|
163
|
-
}
|
|
164
|
-
/**
|
|
165
|
-
* Categorize JSON-Schema-based connector policies into matching and non-matching
|
|
166
|
-
* groups based on dataset compatibility.
|
|
167
|
-
*
|
|
168
|
-
* @param policies - JSON-Schema-based connector policies.
|
|
169
|
-
* @param dataset - Dataset whose schema is used for validation.
|
|
170
|
-
* @param attributes - Attributes associated with the dataset.
|
|
171
|
-
* @returns Object with matching policies, nonMatching policies, and skip details.
|
|
172
|
-
*/
|
|
173
|
-
export function categorizePolicies(policies, dataset, attributes) {
|
|
174
|
-
const evaluations = evaluateJsonSchemaPolicies(dataset, attributes, policies);
|
|
175
|
-
const matching = [];
|
|
176
|
-
const nonMatching = [];
|
|
177
|
-
const skippedDetails = [];
|
|
178
|
-
for (const evalResult of evaluations) {
|
|
179
|
-
const { policy, isValid, errors } = evalResult;
|
|
180
|
-
if (isValid) {
|
|
181
|
-
matching.push(policy);
|
|
182
|
-
}
|
|
183
|
-
else {
|
|
184
|
-
nonMatching.push(policy);
|
|
185
|
-
skippedDetails.push({
|
|
186
|
-
name: policy.name,
|
|
187
|
-
reason: "Dataset schema not compatible with policy JSON Schema",
|
|
188
|
-
details: errors,
|
|
189
|
-
});
|
|
190
|
-
}
|
|
191
|
-
}
|
|
192
|
-
return { matching, nonMatching, skippedDetails };
|
|
193
|
-
}
|
|
194
|
-
/**
|
|
195
|
-
* Helper that evaluates JSON-Schema-based policies and returns branch-level
|
|
196
|
-
* matches plus scheduling information.
|
|
197
|
-
*
|
|
198
|
-
* This is intended to be consumed by a separate SQL builder layer or for
|
|
199
|
-
* inspection of branch-level match details.
|
|
200
|
-
*
|
|
201
|
-
* @param dataset - The dataset against which policies should be evaluated.
|
|
202
|
-
* @param policies - JSON-Schema-based connector policies.
|
|
203
|
-
* @param attributes - Attributes associated with the dataset.
|
|
204
|
-
* @returns JsonSchemaPolicyMatchResult with applied/skipped policies and branch matches.
|
|
205
|
-
* @throws If no policies are compatible with the dataset.
|
|
206
|
-
*/
|
|
207
|
-
export function buildPolicySqlWithJsonSchema(dataset, policies, attributes) {
|
|
208
|
-
const evaluations = evaluateJsonSchemaPolicies(dataset, attributes, policies);
|
|
209
|
-
const appliedPolicies = [];
|
|
210
|
-
const skippedPolicies = [];
|
|
211
|
-
const warnings = [];
|
|
212
|
-
const branchMatches = [];
|
|
213
|
-
for (const evalResult of evaluations) {
|
|
214
|
-
const { policy, isValid, matches, errors } = evalResult;
|
|
215
|
-
if (!isValid) {
|
|
216
|
-
skippedPolicies.push({
|
|
217
|
-
name: policy.name,
|
|
218
|
-
reason: "Dataset schema not compatible with policy JSON Schema",
|
|
219
|
-
details: errors,
|
|
220
|
-
});
|
|
221
|
-
continue;
|
|
222
|
-
}
|
|
223
|
-
appliedPolicies.push(policy.name);
|
|
224
|
-
branchMatches.push({
|
|
225
|
-
policyName: policy.name,
|
|
226
|
-
branches: matches,
|
|
227
|
-
});
|
|
228
|
-
}
|
|
229
|
-
if (appliedPolicies.length === 0) {
|
|
230
|
-
const errorMessage = policies.length === 0
|
|
231
|
-
? "No policies provided"
|
|
232
|
-
: `No policies are compatible with dataset "${dataset.display_name || dataset.name}".`;
|
|
233
|
-
const details = skippedPolicies.length > 0
|
|
234
|
-
? [`Skipped policies: ${skippedPolicies.map((p) => p.name).join(", ")}`]
|
|
235
|
-
: [];
|
|
236
|
-
throw new Error(`${errorMessage}\n${details.join("\n")}`);
|
|
237
|
-
}
|
|
238
|
-
if (skippedPolicies.length > 0) {
|
|
239
|
-
warnings.push(`Skipped ${skippedPolicies.length} incompatible policies: ${skippedPolicies
|
|
240
|
-
.map((p) => p.name)
|
|
241
|
-
.join(", ")}`);
|
|
242
|
-
}
|
|
243
|
-
const refreshSchedules = evaluations
|
|
244
|
-
.filter((e) => e.isValid && e.policy.metadata.refresh_schedule?.max)
|
|
245
|
-
.map((e) => e.policy.metadata.refresh_schedule.max);
|
|
246
|
-
const refresh_schedule = getSmallestRefreshSchedule(refreshSchedules);
|
|
247
|
-
return {
|
|
248
|
-
appliedPolicies,
|
|
249
|
-
skippedPolicies,
|
|
250
|
-
warnings,
|
|
251
|
-
refresh_schedule,
|
|
252
|
-
branchMatches,
|
|
253
|
-
};
|
|
254
|
-
}
|
|
255
|
-
/**
|
|
256
|
-
* Uses JSON Schema branch matching to determine valid policies, then builds
|
|
257
|
-
* SELECT and WHERE SQL fragments.
|
|
258
|
-
*
|
|
259
|
-
* @param dataset - Dataset for which to evaluate policies and produce SQL.
|
|
260
|
-
* @param policies - JSON-Schema-based connector policies.
|
|
261
|
-
* @param attributes - Attributes associated with the dataset.
|
|
262
|
-
* @returns PolicyMatchResult with select, where, applied/skipped policies, warnings, and refresh schedule.
|
|
263
|
-
*/
|
|
264
|
-
export function buildPolicySqlWithValidation(dataset, policies, attributes) {
|
|
265
|
-
const matchResult = buildPolicySqlWithJsonSchema(dataset, policies, attributes);
|
|
266
|
-
const attributeByName = new Map();
|
|
267
|
-
for (const attr of attributes) {
|
|
268
|
-
attributeByName.set(attr.name, attr);
|
|
269
|
-
}
|
|
270
|
-
const sqlFragments = buildSqlFromBranchMatches(matchResult.branchMatches, attributeByName, dataset.name);
|
|
271
|
-
return {
|
|
272
|
-
...sqlFragments,
|
|
273
|
-
appliedPolicies: matchResult.appliedPolicies,
|
|
274
|
-
skippedPolicies: matchResult.skippedPolicies,
|
|
275
|
-
warnings: matchResult.warnings,
|
|
276
|
-
refresh_schedule: matchResult.refresh_schedule,
|
|
277
|
-
};
|
|
278
|
-
}
|
|
279
|
-
/**
|
|
280
|
-
* Build SQL fragments without validation. Assumes you already filtered the policies
|
|
281
|
-
* however you want (e.g. via categorizePolicies). Builds SELECT and WHERE SQL
|
|
282
|
-
* fragments from all provided policies.
|
|
283
|
-
*
|
|
284
|
-
* @param dataset - Dataset for which to build SQL fragments.
|
|
285
|
-
* @param policies - JSON-Schema-based connector policies.
|
|
286
|
-
* @param attributes - Attributes associated with the dataset.
|
|
287
|
-
* @returns PolicySqlFragments with select and where arrays.
|
|
288
|
-
*/
|
|
289
|
-
export function buildPolicySql(dataset, policies, attributes) {
|
|
290
|
-
const evaluations = evaluateJsonSchemaPolicies(dataset, attributes, policies);
|
|
291
|
-
const attributeByName = new Map();
|
|
292
|
-
for (const attr of attributes) {
|
|
293
|
-
attributeByName.set(attr.name, attr);
|
|
294
|
-
}
|
|
295
|
-
const branchMatches = [];
|
|
296
|
-
for (const evalResult of evaluations) {
|
|
297
|
-
if (evalResult.isValid) {
|
|
298
|
-
branchMatches.push({
|
|
299
|
-
policyName: evalResult.policy.name,
|
|
300
|
-
branches: evalResult.matches,
|
|
301
|
-
});
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
return buildSqlFromBranchMatches(branchMatches, attributeByName, dataset.name);
|
|
305
|
-
}
|