@narrative.io/data-collaboration-sdk-ts 2.66.0 → 2.68.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.
@@ -58,11 +58,10 @@ function traverseSatisfiedStructure(node, attributeByName, visitor) {
58
58
  return;
59
59
  }
60
60
  if ("anyOf" in node && Array.isArray(node.anyOf)) {
61
- // For anyOf, only traverse the FIRST satisfied branch
61
+ // For anyOf, traverse ALL satisfied branches
62
62
  for (const child of node.anyOf) {
63
63
  if (isBranchSatisfied(child, attributeByName)) {
64
64
  traverseSatisfiedStructure(child, attributeByName, visitor);
65
- return; // Only process the first satisfied branch
66
65
  }
67
66
  }
68
67
  return;
@@ -1,4 +1,6 @@
1
+ import { normalizePathValue } from "../utils/path-helpers";
1
2
  import { buildSelectAlias, resolveAttributeExpression, } from "../utils/sql-helpers";
3
+ import { filterToSql } from "./filter-builder";
2
4
  import { traversePolicy } from "./policy-traverser";
3
5
  import { validatePolicyAgainstDataset } from "./policy-validator";
4
6
  function buildAttributeIndexes(attributes) {
@@ -41,14 +43,134 @@ function getSmallestRefreshSchedule(schedules) {
41
43
  }
42
44
  return smallestSchedule;
43
45
  }
46
+ /**
47
+ * Builds structured WHERE clauses for a single branch node
48
+ */
49
+ function buildBranchWhere(node, attributeByName, datasetName) {
50
+ if (!node || typeof node !== "object") {
51
+ return null;
52
+ }
53
+ // Handle field nodes (leaf nodes with attributes)
54
+ if ("field" in node) {
55
+ const extendedNode = node;
56
+ const field = extendedNode.field;
57
+ const attributeName = field.attribute_name;
58
+ const attribute = attributeByName.get(attributeName);
59
+ if (!attribute) {
60
+ return null;
61
+ }
62
+ const clauses = [];
63
+ // Add IS NOT NULL filters for additional_required_properties
64
+ if (field.additional_required_properties) {
65
+ for (const path of field.additional_required_properties) {
66
+ const pathStr = normalizePathValue(path);
67
+ const expression = `company_data."${datasetName}"."_rosetta_stone"."${attribute.name}"."${pathStr}"`;
68
+ clauses.push(`(${expression} IS NOT NULL)`);
69
+ }
70
+ }
71
+ // Add filters
72
+ if (extendedNode.filters) {
73
+ for (const filter of extendedNode.filters) {
74
+ const sql = filterToSql(filter, attributeByName, datasetName);
75
+ clauses.push(sql);
76
+ }
77
+ }
78
+ // If only one clause, return as string
79
+ if (clauses.length === 0) {
80
+ return null;
81
+ }
82
+ if (clauses.length === 1) {
83
+ return clauses[0];
84
+ }
85
+ // Multiple clauses for a single field - AND them together
86
+ return {
87
+ operation: "AND",
88
+ fragments: clauses,
89
+ };
90
+ }
91
+ // Handle anyOf nodes
92
+ if ("anyOf" in node && Array.isArray(node.anyOf)) {
93
+ const satisfiedBranches = [];
94
+ for (const child of node.anyOf) {
95
+ if (isBranchSatisfied(child, attributeByName)) {
96
+ const branchWhere = buildBranchWhere(child, attributeByName, datasetName);
97
+ if (branchWhere) {
98
+ satisfiedBranches.push(branchWhere);
99
+ }
100
+ }
101
+ }
102
+ if (satisfiedBranches.length === 0) {
103
+ return null;
104
+ }
105
+ if (satisfiedBranches.length === 1) {
106
+ return satisfiedBranches[0];
107
+ }
108
+ // Multiple satisfied anyOf branches - OR them together
109
+ return {
110
+ operation: "OR",
111
+ fragments: satisfiedBranches,
112
+ };
113
+ }
114
+ // Handle allOf nodes
115
+ if ("allOf" in node && Array.isArray(node.allOf)) {
116
+ const allBranches = [];
117
+ for (const child of node.allOf) {
118
+ const branchWhere = buildBranchWhere(child, attributeByName, datasetName);
119
+ if (branchWhere) {
120
+ allBranches.push(branchWhere);
121
+ }
122
+ }
123
+ if (allBranches.length === 0) {
124
+ return null;
125
+ }
126
+ if (allBranches.length === 1) {
127
+ return allBranches[0];
128
+ }
129
+ // Multiple allOf branches - AND them together
130
+ return {
131
+ operation: "AND",
132
+ fragments: allBranches,
133
+ };
134
+ }
135
+ return null;
136
+ }
137
+ /**
138
+ * Checks if a branch is satisfied (all required attributes exist)
139
+ */
140
+ function isBranchSatisfied(node, attributeByName) {
141
+ if (!node || typeof node !== "object") {
142
+ return true;
143
+ }
144
+ if ("field" in node) {
145
+ const field = node
146
+ .field;
147
+ if (field.type === "attribute") {
148
+ const attributeName = field.attribute_name;
149
+ return attributeByName.has(attributeName);
150
+ }
151
+ return true;
152
+ }
153
+ if ("anyOf" in node && Array.isArray(node.anyOf)) {
154
+ return node.anyOf.some((child) => isBranchSatisfied(child, attributeByName));
155
+ }
156
+ if ("allOf" in node && Array.isArray(node.allOf)) {
157
+ return node.allOf.every((child) => isBranchSatisfied(child, attributeByName));
158
+ }
159
+ return true;
160
+ }
44
161
  function buildPolicySqlUsingIndexes(policies, attributeByName, datasetName) {
45
162
  const globalRequiredAttributePaths = new Map();
46
- const policyWhereClauses = [];
163
+ const policyWhereGroups = [];
47
164
  for (const policy of policies) {
48
165
  const policyRequiredAttributePaths = new Map();
49
166
  const policyWhereClausesSet = new Set();
167
+ // Still use traversePolicy to collect required attributes for SELECT
50
168
  traversePolicy(policy, attributeByName, policyRequiredAttributePaths, policyWhereClausesSet, datasetName);
51
- policyWhereClauses.push(Array.from(policyWhereClausesSet));
169
+ // Build structured WHERE for this policy
170
+ const policyWhere = buildBranchWhere(policy.policy.definition.structure, attributeByName, datasetName);
171
+ if (policyWhere) {
172
+ policyWhereGroups.push(policyWhere);
173
+ }
52
174
  for (const [attributeName, paths] of policyRequiredAttributePaths) {
53
175
  const globalPaths = globalRequiredAttributePaths.get(attributeName) ?? new Set();
54
176
  for (const path of paths) {
@@ -69,9 +191,26 @@ function buildPolicySqlUsingIndexes(policies, attributeByName, datasetName) {
69
191
  const alias = buildSelectAlias(attributeName, "");
70
192
  selectClauses.add(`${expression} AS ${alias}`);
71
193
  }
194
+ // Build the root AND structure
195
+ // Root level is always AND (combines policies)
196
+ // Each policy's branches are already OR'd/AND'd by buildBranchWhere
197
+ let where;
198
+ if (policyWhereGroups.length === 0) {
199
+ where = [];
200
+ }
201
+ else {
202
+ // Wrap all policies in root AND
203
+ // Each policy in policyWhereGroups is already a complete policy structure
204
+ where = [
205
+ {
206
+ fragments: policyWhereGroups,
207
+ operation: "AND",
208
+ },
209
+ ];
210
+ }
72
211
  return {
73
212
  select: Array.from(selectClauses),
74
- where: policyWhereClauses,
213
+ where,
75
214
  };
76
215
  }
77
216
  export function buildPolicySqlWithValidation(dataset, policies, attributes) {
@@ -7,9 +7,13 @@ export type ExtendedAttributeNode = Extract<StructureNode, {
7
7
  export type PolicyFilter = NonNullable<PolicyDefinition["filters"]>[number];
8
8
  export type PathValue = NonNullable<ExtendedAttributeNode["field"]["additional_required_properties"]>[number];
9
9
  export type AttributePathIndex = Map<string, Set<string>>;
10
+ export interface PolicySqlGroup {
11
+ fragments: Array<string | PolicySqlGroup>;
12
+ operation: "AND" | "OR";
13
+ }
10
14
  export interface PolicySqlFragments {
11
15
  select: string[];
12
- where: string[][];
16
+ where: Array<PolicySqlGroup | string>;
13
17
  }
14
18
  export interface PolicyMatchResult extends PolicySqlFragments {
15
19
  appliedPolicies: string[];
@@ -1,4 +1,4 @@
1
1
  export { buildPolicySql, buildPolicySqlWithValidation, categorizePolicies, } from "./core/sql-builder";
2
- export type { AttributePathIndex, ExtendedAttributeNode, PathValue, PolicyFilter, PolicyMatchResult, PolicySqlFragments, StructureNode, } from "./core/types";
2
+ export type { AttributePathIndex, ExtendedAttributeNode, PathValue, PolicyFilter, PolicyMatchResult, PolicySqlFragments, PolicySqlGroup, StructureNode, } from "./core/types";
3
3
  export type { CollaborationPolicyInput, CollaborationPolicyType, } from "./types";
4
4
  export { buildCollaborationPolicyJsonSchema, CollaborationPolicy, } from "./types";
@@ -1,5 +1,5 @@
1
1
  import type { Attribute } from "../../attributes/types";
2
+ import type { PolicySqlGroup } from "../core/types";
2
3
  export declare function resolveAttributeExpression(attribute: Attribute, path: string, datasetName: string): string;
3
4
  export declare function buildSelectAlias(attributeName: string, path: string): string;
4
- export declare function buildPolicyWhereGroups(policyWhereClauses: string[][]): string[];
5
- export declare function formatWhereClause(policyWhereClauses: string[][]): string;
5
+ export declare function formatWhereClause(where: Array<PolicySqlGroup | string>): string;
@@ -35,29 +35,35 @@ export function buildSelectAlias(attributeName, path) {
35
35
  const suffix = path.replace(/[^a-zA-Z0-9]+/g, "_");
36
36
  return quoteIdentifier(`${attributeName}_${suffix}`);
37
37
  }
38
- export function buildPolicyWhereGroups(policyWhereClauses) {
39
- const groups = [];
40
- for (const clauses of policyWhereClauses) {
41
- if (clauses.length === 0) {
42
- continue;
43
- }
44
- const firstClause = clauses[0];
45
- if (firstClause === undefined) {
46
- continue;
47
- }
48
- groups.push(clauses.length === 1 ? firstClause : `(${clauses.join(" AND ")})`);
38
+ /**
39
+ * Recursively builds SQL string from PolicySqlGroup structure
40
+ */
41
+ function buildSqlFromGroup(item) {
42
+ if (typeof item === "string") {
43
+ return item;
49
44
  }
50
- return groups;
45
+ const { fragments, operation } = item;
46
+ const separator = operation === "AND" ? " AND " : " OR ";
47
+ const parts = fragments.map(buildSqlFromGroup);
48
+ if (parts.length === 0) {
49
+ return "";
50
+ }
51
+ if (parts.length === 1) {
52
+ return parts[0];
53
+ }
54
+ return `(${parts.join(separator)})`;
51
55
  }
52
- export function formatWhereClause(policyWhereClauses) {
53
- const groups = buildPolicyWhereGroups(policyWhereClauses);
54
- if (groups.length === 0) {
56
+ export function formatWhereClause(where) {
57
+ if (where.length === 0) {
55
58
  return "";
56
59
  }
57
- if (groups.length === 1) {
58
- return `\nWHERE\n ${groups[0]}`;
60
+ const sqlParts = where
61
+ .map(buildSqlFromGroup)
62
+ .filter((part) => part.length > 0);
63
+ if (sqlParts.length === 0) {
64
+ return "";
59
65
  }
60
- return `\nWHERE\n ${groups.join(" OR\n ")}`;
66
+ return `\nWHERE\n ${sqlParts.join("\n ")}`;
61
67
  }
62
68
  function quoteIdentifier(identifier) {
63
69
  const safe = identifier.replace(/"/g, "");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narrative.io/data-collaboration-sdk-ts",
3
- "version": "2.66.0",
3
+ "version": "2.68.0",
4
4
  "main": "build/index.js",
5
5
  "repository": "github:narrative-io/data-collaboration-sdk-ts",
6
6
  "source": "src/index.ts",