@narrative.io/data-collaboration-sdk-ts 0.0.64 → 0.0.66

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/index.d.ts CHANGED
@@ -21,10 +21,12 @@ import { DataStreamsApi, type DataStream } from './data-streams';
21
21
  import { ForecastApi, type ForecastRequest, type ForecastResponse, type CostForecastRequest, type CostForecastResponse, type JobState } from './forecast';
22
22
  import { ContractsApi, type ContractDetails, type CustomerContract, type PaymentMethod, type ContractRateView } from './contracts';
23
23
  import { getNqlObject, buildNql } from './nql/NQLParser';
24
+ import { convertNqlObjectToForecastBody } from './nql/DataRulesConverter';
24
25
  import { AuthenticationApi, type LoginRequest, type LoginResponse, type RegisterRequest, type RegisterResponse, type LoggedInUser, type StytchToken } from './authentication';
25
26
  import { AppsApi, type App } from './apps';
27
+ import type { ForecastBody } from './nql/types';
26
28
  declare class NarrativeApi extends BaseApi {
27
29
  }
28
30
  interface NarrativeApi extends BaseApi, HealthCheckApi, DatasetApi, RosettaStoneApi, AttributeApi, PingApi, CompanyInfoApi, InstallationsApi, UploadsApi, ResourceApi, NqlApi, DataStreamsApi, ForecastApi, ContractsApi, AuthenticationApi {
29
31
  }
30
- export { NarrativeApi, AttributeApi, HealthCheckApi, DatasetApi, PingApi, ProductsApi, CompanyInfoApi, InstallationsApi, UploadsApi, ResourceApi, NqlApi, DataStreamsApi, ForecastApi, ContractsApi, AuthenticationApi, AppsApi, type App, type Resource, type UploadsResponse, type PingStatus, type Attribute, type Environment, type Config, type ApiRecords, type Dataset, type HealthCheckResult, type SampleRecords, type DatasetTableSummary, type DatasetTableSummaryAPIResponse, type Product, type MonetaryAmount, type CompanyInfo, type Installation, type FilePerSnapshotResponse, type BucketCreationRequest, type UpdateAccessTypeRequest, type Nql, type NqlField, type NqlResult, type NqlQueryInput, type NqlCompileResult, type NqlExpression, type NqlWhere, type NqlFilterExpression, type NqlBooleanExpression, type NqlFilterBinaryExpression, type NqlFilterUnaryExpression, type DataStream, type ForecastRequest, type ForecastResponse, type CostForecastRequest, type CostForecastResponse, type JobState, type ContractDetails, type CustomerContract, type PaymentMethod, type ContractRateView, type LoginRequest, type LoginResponse, type RegisterRequest, type RegisterResponse, type LoggedInUser, type StytchToken, getNqlObject, buildNql };
32
+ export { NarrativeApi, AttributeApi, HealthCheckApi, DatasetApi, PingApi, ProductsApi, CompanyInfoApi, InstallationsApi, UploadsApi, ResourceApi, NqlApi, DataStreamsApi, ForecastApi, ContractsApi, AuthenticationApi, AppsApi, type App, type Resource, type UploadsResponse, type PingStatus, type Attribute, type Environment, type Config, type ApiRecords, type Dataset, type HealthCheckResult, type SampleRecords, type DatasetTableSummary, type DatasetTableSummaryAPIResponse, type Product, type MonetaryAmount, type CompanyInfo, type Installation, type FilePerSnapshotResponse, type BucketCreationRequest, type UpdateAccessTypeRequest, type Nql, type NqlField, type NqlResult, type NqlQueryInput, type NqlCompileResult, type NqlExpression, type NqlWhere, type NqlFilterExpression, type NqlBooleanExpression, type NqlFilterBinaryExpression, type NqlFilterUnaryExpression, type DataStream, type ForecastRequest, type ForecastResponse, type CostForecastRequest, type CostForecastResponse, type JobState, type ContractDetails, type CustomerContract, type PaymentMethod, type ContractRateView, type LoginRequest, type LoginResponse, type RegisterRequest, type RegisterResponse, type LoggedInUser, type StytchToken, getNqlObject, buildNql, convertNqlObjectToForecastBody, type ForecastBody };
package/build/index.js CHANGED
@@ -15,9 +15,10 @@ import { DataStreamsApi } from './data-streams';
15
15
  import { ForecastApi } from './forecast';
16
16
  import { ContractsApi } from './contracts';
17
17
  import { getNqlObject, buildNql } from './nql/NQLParser';
18
+ import { convertNqlObjectToForecastBody } from './nql/DataRulesConverter';
18
19
  import { AuthenticationApi } from './authentication';
19
20
  import { AppsApi } from './apps';
20
21
  class NarrativeApi extends BaseApi {
21
22
  }
22
23
  applyMixins(NarrativeApi, [BaseApi, HealthCheckApi, DatasetApi, RosettaStoneApi, AttributeApi, PingApi, CompanyInfoApi, InstallationsApi, UploadsApi, ResourceApi, NqlApi, DataStreamsApi, ForecastApi, ContractsApi, AuthenticationApi]);
23
- export { NarrativeApi, AttributeApi, HealthCheckApi, DatasetApi, PingApi, ProductsApi, CompanyInfoApi, InstallationsApi, UploadsApi, ResourceApi, NqlApi, DataStreamsApi, ForecastApi, ContractsApi, AuthenticationApi, AppsApi, getNqlObject, buildNql };
24
+ export { NarrativeApi, AttributeApi, HealthCheckApi, DatasetApi, PingApi, ProductsApi, CompanyInfoApi, InstallationsApi, UploadsApi, ResourceApi, NqlApi, DataStreamsApi, ForecastApi, ContractsApi, AuthenticationApi, AppsApi, getNqlObject, buildNql, convertNqlObjectToForecastBody };
@@ -0,0 +1,8 @@
1
+ import type { ForecastBody, Nql } from './types';
2
+ /**
3
+ * Convert an NQL object to a Forecast body.
4
+ * @param {Nql} nqlObject - The NQL object to convert.
5
+ * @returns {Promise<ForecastBody>} - Returns a promise that resolves with the converted ForecastBody.
6
+ * @throws {Error} - Throws an error if price filter conditions are not met.
7
+ */
8
+ export declare function convertNqlObjectToForecastBody(nqlObject: Nql): Promise<ForecastBody>;
@@ -0,0 +1,188 @@
1
+ import { AttributeApi } from '../attributes';
2
+ /**
3
+ * Convert an NQL object to a Forecast body.
4
+ * @param {Nql} nqlObject - The NQL object to convert.
5
+ * @returns {Promise<ForecastBody>} - Returns a promise that resolves with the converted ForecastBody.
6
+ * @throws {Error} - Throws an error if price filter conditions are not met.
7
+ */
8
+ export async function convertNqlObjectToForecastBody(nqlObject) {
9
+ const price = getPriceFromNql(nqlObject);
10
+ const fields = getFieldsFromNql(nqlObject);
11
+ const forecastBody = {
12
+ as_of: new Date().toISOString(),
13
+ details: {
14
+ type: 'marketplace',
15
+ pricing: price,
16
+ data_rules: {
17
+ attributes: await getAttributesFromFields(fields)
18
+ }
19
+ }
20
+ };
21
+ return forecastBody;
22
+ }
23
+ /**
24
+ * Get attributes from an array of fields.
25
+ * @param {DataRulesAttributesField[]} fields - An array of fields.
26
+ * @returns {Promise<DataRulesAttributes[]>} - Returns a promise that resolves with an array of DataRulesAttributes.
27
+ * @throws {Error} - Throws an error if attribute is not found.
28
+ */
29
+ async function getAttributesFromFields(fields) {
30
+ const attributesArray = [];
31
+ const attributesApi = new AttributeApi({
32
+ environment: 'prod'
33
+ });
34
+ const attributes = await attributesApi.getAttributes();
35
+ const uniqueAttributes = [...new Set(fields.map((field) => field.field.split('.')[0]))].filter((att) => !att.startsWith('_'));
36
+ uniqueAttributes.forEach((att) => {
37
+ const attributeId = attributes.records.find((attribute) => attribute.name === att)?.id;
38
+ if (attributeId === undefined) {
39
+ throw new Error(`Attribute ${att} not found`);
40
+ }
41
+ attributesArray.push({
42
+ attribute_id: attributeId,
43
+ fields: fields.filter((field) => field.field.split('.')[0] === att),
44
+ optional: fields.some(field => field.field.startsWith(att + '.') && field.filter !== undefined)
45
+ });
46
+ });
47
+ ensureAtLeastOneRequired(attributesArray);
48
+ return attributesArray;
49
+ }
50
+ /**
51
+ * Ensures that at least one object within the provided array has its `optional` property set to `false`.
52
+ * If none of the objects have `optional` set to `false`, it randomly sets one object's `optional` property to `false`.
53
+ *
54
+ * @param {DataRulesAttributes[]} dataRulesAttributes - An array of DataRulesAttributes objects.
55
+ * @returns {void}
56
+ *
57
+ * @example
58
+ * const dataRulesAttributesArray = [
59
+ * { fields: [], optional: true, attribute_id: 1 },
60
+ * { fields: [], optional: true, attribute_id: 2 }
61
+ * ];
62
+ *
63
+ * ensureAtLeastOneRequired(dataRulesAttributesArray);
64
+ *
65
+ * // The array is now guaranteed to have at least one object with `optional` set to `false`.
66
+ */
67
+ function ensureAtLeastOneRequired(dataRulesAttributes) {
68
+ const hasMandatory = dataRulesAttributes.some(item => !item.optional);
69
+ if (!hasMandatory) {
70
+ const randomIndex = Math.floor(Math.random() * dataRulesAttributes.length);
71
+ dataRulesAttributes[randomIndex].optional = false;
72
+ }
73
+ }
74
+ /**
75
+ * Extracts the price from an NQL object.
76
+ * @param {Nql} nqlObject - The NQL object to extract the price from.
77
+ * @returns {QueryPricing} - Returns the extracted price in micro cents.
78
+ * @throws {Error} - Throws an error if price filter conditions are not met.
79
+ */
80
+ function getPriceFromNql(nqlObject) {
81
+ console.log('yyy', JSON.stringify(nqlObject.where.filterExpressions));
82
+ const priceFilter = nqlObject.where.filterExpressions.filter((expr) => ('left' in expr && typeof expr.left === 'object' && expr.left.field === '_price_cpm_usd') || ('right' in expr && typeof expr.right === 'object' && expr.right.field === '_price_cpm_usd'));
83
+ if (priceFilter.length !== 1) {
84
+ throw new Error('Expected exactly one price filter');
85
+ }
86
+ if (typeof priceFilter[0].right === 'number') {
87
+ return {
88
+ micro_cents_usd: priceFilter[0].right * 100000000
89
+ };
90
+ }
91
+ else if (typeof priceFilter[0].left === 'number') {
92
+ return {
93
+ micro_cents_usd: priceFilter[0].left * 100000000
94
+ };
95
+ }
96
+ else {
97
+ throw new Error('Expected one side of price filter to be a number');
98
+ }
99
+ }
100
+ /**
101
+ * Extract fields from an NQL object.
102
+ * @param {Nql} nqlObject - The NQL object to extract fields from.
103
+ * @returns {DataRulesAttributesField[]} - Returns an array of DataRulesAttributesField extracted from the NQL object.
104
+ */
105
+ function getFieldsFromNql(nqlObject) {
106
+ const fieldsArray = [];
107
+ // Extract all fields from the 'select' section and mark them as exported.
108
+ nqlObject.select.forEach((field) => {
109
+ fieldsArray.push({ field: field.field, exported: true });
110
+ });
111
+ // Process all filter expressions from the 'where' section.
112
+ nqlObject.where.filterExpressions.forEach((expression) => {
113
+ handleFilterExpression(expression, fieldsArray);
114
+ });
115
+ return fieldsArray;
116
+ }
117
+ /**
118
+ * Handle an NQL filter expression.
119
+ * @param {NqlFilterExpression} expression - The NQL filter expression to handle.
120
+ * @param {DataRulesAttributesField[]} fieldsArray - The array to populate with fields.
121
+ */
122
+ function handleFilterExpression(expression, fieldsArray) {
123
+ if ('field' in expression) {
124
+ updateFieldsArray(expression.field, fieldsArray, constructUnaryFilterExpression(expression));
125
+ }
126
+ else {
127
+ if (typeof expression.right === 'object' && expression.right !== null && 'field' in expression.right) {
128
+ updateFieldsArray(expression.right, fieldsArray, constructBinaryFilterExpression(expression));
129
+ }
130
+ else {
131
+ updateFieldsArray(expression.left, fieldsArray, constructBinaryFilterExpression(expression));
132
+ }
133
+ }
134
+ }
135
+ /**
136
+ * Update the fields array with the provided field and filter expression.
137
+ * @param {any} field - The field to add or update.
138
+ * @param {DataRulesAttributesField[]} fieldsArray - The array of fields to be updated.
139
+ * @param {any} filterExpression - The filter expression to add to the field.
140
+ */
141
+ function updateFieldsArray(field, fieldsArray, filterExpression) {
142
+ const existingEntry = fieldsArray.find((entry) => entry.field === field.field);
143
+ if (existingEntry != null) {
144
+ const updatedEntry = {
145
+ ...existingEntry,
146
+ filter: { expressions: [filterExpression] }
147
+ };
148
+ fieldsArray[fieldsArray.indexOf(existingEntry)] = updatedEntry;
149
+ }
150
+ else {
151
+ fieldsArray.push({ field: field.field, exported: false, filter: { expressions: [filterExpression] } });
152
+ }
153
+ }
154
+ /**
155
+ * Constructs a string representing a unary filter expression.
156
+ * @param {NqlFilterUnaryExpression} filter - The unary filter expression.
157
+ * @returns {string} - Returns a string representation of the unary filter expression.
158
+ */
159
+ function constructUnaryFilterExpression(filter) {
160
+ return `${filter.field.field} ${filter.operator}`;
161
+ }
162
+ /**
163
+ * Constructs a string representing a binary filter expression.
164
+ * @param {NqlFilterBinaryExpression} filter - The binary filter expression.
165
+ * @returns {string} - Returns a string representation of the binary filter expression.
166
+ */
167
+ function constructBinaryFilterExpression(filter) {
168
+ return `${expressionToString(filter.left)} ${filter.operator} ${expressionToString(filter.right)}`;
169
+ }
170
+ /**
171
+ * Converts an expression to a string.
172
+ * @param {string | number | boolean | NqlField} expr - The expression to convert.
173
+ * @returns {string} - Returns the string representation of the expression.
174
+ */
175
+ function expressionToString(expr) {
176
+ if (typeof expr === 'string') {
177
+ return "'expr'";
178
+ }
179
+ else if (typeof expr === 'number') {
180
+ return expr.toString();
181
+ }
182
+ else if (typeof expr === 'boolean') {
183
+ return expr.toString();
184
+ }
185
+ else {
186
+ return expr.field;
187
+ }
188
+ }
@@ -52,7 +52,7 @@ export function getFrom(nql) {
52
52
  * @returns {NqlBooleanExpression[]} - An array of parsed NqlBooleanExpression objects representing the boolean expressions.
53
53
  */
54
54
  function getBooleanExpressionsFromWhere(where) {
55
- const booleanRegex = /\b(OR|AND)\b/ig;
55
+ const booleanRegex = /\s(OR|AND)\s/ig;
56
56
  const matches = [];
57
57
  let boolMatch;
58
58
  while ((boolMatch = booleanRegex.exec(where)) !== null) {
@@ -68,12 +68,12 @@ function getBooleanExpressionsFromWhere(where) {
68
68
  */
69
69
  function getFilterExpressionsFromWhere(where) {
70
70
  let matches = [];
71
- const hasBoolean = /\b(?:OR|AND)\b/ig.test(where);
71
+ const hasBoolean = /\s(?:OR|AND)\s/ig.test(where);
72
72
  if (!hasBoolean) {
73
73
  matches.push(getFilterExpressionFromString(where));
74
74
  }
75
75
  else {
76
- const expressions = where.split(/\b(?:OR|AND)\b/ig).map((t) => t.trim());
76
+ const expressions = where.split(/\s(?:OR|AND)\s/ig).map((t) => t.trim());
77
77
  matches = matches.concat(expressions.map((e) => getFilterExpressionFromString(e)));
78
78
  }
79
79
  return NqlFilterExpressionObj.array().parse(matches);
@@ -165,7 +165,7 @@ export function getFilterExpressionFromString(str) {
165
165
  operator
166
166
  });
167
167
  }
168
- throw new Error('Invalid filter expression');
168
+ throw new Error(`Invalid filter expression: ${str}`);
169
169
  }
170
170
  /**
171
171
  * Parses the pre-select expression from the given NQL (Natural Query Language) string.
@@ -320,11 +320,12 @@ export function buildField(field) {
320
320
  }
321
321
  }
322
322
  export function getNqlObject(nql, type) {
323
- const explain = getPreSelect(nql);
324
- const limit = getLimit(nql);
325
- const where = getWhere(nql);
326
- const from = getFrom(nql);
327
- const select = getSelect(nql);
323
+ const cleanedNql = nql.trim().replace(/\n/g, ' ').replace(/\s+/g, ' ');
324
+ const explain = getPreSelect(cleanedNql);
325
+ const limit = getLimit(cleanedNql);
326
+ const where = getWhere(cleanedNql);
327
+ const from = getFrom(cleanedNql);
328
+ const select = getSelect(cleanedNql);
328
329
  const parsed = NqlObj.parse({ where, from, explain, select, limit });
329
330
  if (type === undefined || type === 'full') {
330
331
  return parsed;
@@ -1282,4 +1282,32 @@ export interface NqlCompileResult {
1282
1282
  sql: string;
1283
1283
  mappingErrors?: NqlMappingErrors;
1284
1284
  }
1285
+ export interface ForecastBody {
1286
+ as_of: string;
1287
+ details: QueryDetails;
1288
+ }
1289
+ export interface QueryDetails {
1290
+ type: 'marketplace';
1291
+ pricing: QueryPricing;
1292
+ data_rules: DataRules;
1293
+ }
1294
+ export interface QueryPricing {
1295
+ micro_cents_usd: number;
1296
+ }
1297
+ export interface DataRules {
1298
+ attributes: DataRulesAttributes[];
1299
+ }
1300
+ export interface DataRulesAttributes {
1301
+ fields: DataRulesAttributesField[];
1302
+ optional: boolean;
1303
+ attribute_id: number;
1304
+ }
1305
+ export interface DataRulesAttributesField {
1306
+ field: string;
1307
+ filter?: unknown;
1308
+ exported: boolean;
1309
+ }
1310
+ export interface DataRulesAttributesFieldFilter {
1311
+ expression: string[];
1312
+ }
1285
1313
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@narrative.io/data-collaboration-sdk-ts",
3
- "version": "0.0.64",
3
+ "version": "0.0.66",
4
4
  "main": "build/index.js",
5
5
  "repository": "github:narrative-io/data-collaboration-sdk-ts",
6
6
  "source": "src/index.ts",