@narrative.io/data-collaboration-sdk-ts 0.0.65 → 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
+ }
@@ -128,7 +128,6 @@ function parseString(value) {
128
128
  * @returns {NqlField | string} - The parsed NqlField object or the original field string if parsing fails.
129
129
  */
130
130
  export function getFilterExpressionFromString(str) {
131
- console.log(str);
132
131
  const comparison = ['<=', '>=', '!=', '=', '>', '<'];
133
132
  const containsComparison = comparison.find((character) => str.includes(character));
134
133
  if (containsComparison !== undefined) {
@@ -321,11 +320,12 @@ export function buildField(field) {
321
320
  }
322
321
  }
323
322
  export function getNqlObject(nql, type) {
324
- const explain = getPreSelect(nql);
325
- const limit = getLimit(nql);
326
- const where = getWhere(nql);
327
- const from = getFrom(nql);
328
- 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);
329
329
  const parsed = NqlObj.parse({ where, from, explain, select, limit });
330
330
  if (type === undefined || type === 'full') {
331
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.65",
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",