@unito/integration-api 4.2.0 → 4.2.2
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/dist/src/guards.d.ts +18 -0
- package/dist/src/guards.js +3 -3
- package/dist/src/index.cjs +170 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/jsonPathHelpers.d.ts +5 -0
- package/dist/src/jsonPathHelpers.js +166 -0
- package/dist/src/types.d.ts +6 -0
- package/package.json +1 -1
package/dist/src/guards.d.ts
CHANGED
|
@@ -1,4 +1,22 @@
|
|
|
1
1
|
import * as Api from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Checks if the input is a record<unknown, unknown>
|
|
4
|
+
* @param potentialObject - The value to check.
|
|
5
|
+
* @returns True if the value is a record, false otherwise.
|
|
6
|
+
*/
|
|
7
|
+
export declare function isObject(potentialObject: unknown): potentialObject is Record<string, unknown>;
|
|
8
|
+
/**
|
|
9
|
+
* Checks if the input is a string.
|
|
10
|
+
* @param potentialString - The value to check.
|
|
11
|
+
* @returns True if the value is a string, false otherwise.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isString(potentialString: unknown): potentialString is string;
|
|
14
|
+
/**
|
|
15
|
+
* Checks if the input is undefined.
|
|
16
|
+
* @param potentialUndefined - The value to check.
|
|
17
|
+
* @returns True if the value is undefined, false otherwise.
|
|
18
|
+
*/
|
|
19
|
+
export declare function isUndefined(potentialUndefined: unknown): potentialUndefined is undefined;
|
|
2
20
|
/**
|
|
3
21
|
* Checks if the input is an Api.ItemSummary object.
|
|
4
22
|
* @param potentialItemSummary - The value to check.
|
package/dist/src/guards.js
CHANGED
|
@@ -4,7 +4,7 @@ import * as Api from './types.js';
|
|
|
4
4
|
* @param potentialObject - The value to check.
|
|
5
5
|
* @returns True if the value is a record, false otherwise.
|
|
6
6
|
*/
|
|
7
|
-
function isObject(potentialObject) {
|
|
7
|
+
export function isObject(potentialObject) {
|
|
8
8
|
return typeof potentialObject === 'object' && potentialObject !== null && !Array.isArray(potentialObject);
|
|
9
9
|
}
|
|
10
10
|
/**
|
|
@@ -12,7 +12,7 @@ function isObject(potentialObject) {
|
|
|
12
12
|
* @param potentialString - The value to check.
|
|
13
13
|
* @returns True if the value is a string, false otherwise.
|
|
14
14
|
*/
|
|
15
|
-
function isString(potentialString) {
|
|
15
|
+
export function isString(potentialString) {
|
|
16
16
|
return typeof potentialString === 'string';
|
|
17
17
|
}
|
|
18
18
|
/**
|
|
@@ -20,7 +20,7 @@ function isString(potentialString) {
|
|
|
20
20
|
* @param potentialUndefined - The value to check.
|
|
21
21
|
* @returns True if the value is undefined, false otherwise.
|
|
22
22
|
*/
|
|
23
|
-
function isUndefined(potentialUndefined) {
|
|
23
|
+
export function isUndefined(potentialUndefined) {
|
|
24
24
|
return potentialUndefined === undefined;
|
|
25
25
|
}
|
|
26
26
|
/**
|
package/dist/src/index.cjs
CHANGED
|
@@ -563,19 +563,189 @@ const fieldTypeCompatibilityMatrix = {
|
|
|
563
563
|
},
|
|
564
564
|
};
|
|
565
565
|
|
|
566
|
+
/**
|
|
567
|
+
* JSONPath parser that returns a relation that is guaranteed to have its schema populated.
|
|
568
|
+
*/
|
|
569
|
+
function findRelationByJSONPath(item, query) {
|
|
570
|
+
const tokens = parseJSONPath(query);
|
|
571
|
+
const schemas = [];
|
|
572
|
+
let current = item;
|
|
573
|
+
for (const token of tokens) {
|
|
574
|
+
if (current === '__self') {
|
|
575
|
+
const previousSchema = schemas[schemas.length - 1];
|
|
576
|
+
if (!previousSchema) {
|
|
577
|
+
throw new Error(`Invalid use of __self`);
|
|
578
|
+
}
|
|
579
|
+
current = previousSchema;
|
|
580
|
+
}
|
|
581
|
+
const result = applyToken(current, token);
|
|
582
|
+
if (isObject(result) && isRelationSchema(result['schema'])) {
|
|
583
|
+
schemas.push(result['schema']);
|
|
584
|
+
}
|
|
585
|
+
current = result;
|
|
586
|
+
}
|
|
587
|
+
if (isRelation(current)) {
|
|
588
|
+
return current;
|
|
589
|
+
}
|
|
590
|
+
if (isReferenceRelation(current) || isRelationSummary(current)) {
|
|
591
|
+
const latestSchema = schemas[schemas.length - 1];
|
|
592
|
+
if (latestSchema === undefined) {
|
|
593
|
+
throw new Error(`No schema found for relation ${current.label}`);
|
|
594
|
+
}
|
|
595
|
+
return { ...current, schema: latestSchema };
|
|
596
|
+
}
|
|
597
|
+
return undefined;
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Parse JSONPath expression into tokens
|
|
601
|
+
*/
|
|
602
|
+
function parseJSONPath(query) {
|
|
603
|
+
const tokens = [];
|
|
604
|
+
let remaining = query;
|
|
605
|
+
// Remove root $ if present
|
|
606
|
+
if (remaining.startsWith('$')) {
|
|
607
|
+
remaining = remaining.substring(1);
|
|
608
|
+
}
|
|
609
|
+
while (remaining.length > 0) {
|
|
610
|
+
// Skip leading dots
|
|
611
|
+
if (remaining.startsWith('.')) {
|
|
612
|
+
remaining = remaining.substring(1);
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
// Parse bracket notation [...]
|
|
616
|
+
if (remaining.startsWith('[')) {
|
|
617
|
+
const bracketMatch = remaining.match(/^\[([^\]]*)\]/);
|
|
618
|
+
if (!bracketMatch) {
|
|
619
|
+
throw new Error(`Unclosed bracket in JSONPath: ${query}`);
|
|
620
|
+
}
|
|
621
|
+
remaining = remaining.substring(bracketMatch[0].length);
|
|
622
|
+
tokens.push(parseBracketExpression(String(bracketMatch[1])));
|
|
623
|
+
continue;
|
|
624
|
+
}
|
|
625
|
+
// Parse property name (until . or [ or end)
|
|
626
|
+
const propertyMatch = remaining.match(/^([^.[]+)/);
|
|
627
|
+
if (propertyMatch) {
|
|
628
|
+
const propertyName = String(propertyMatch[1]);
|
|
629
|
+
remaining = remaining.substring(propertyName.length);
|
|
630
|
+
tokens.push({ type: 'property', name: propertyName });
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
return tokens;
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Parse bracket expression into a token
|
|
637
|
+
*/
|
|
638
|
+
function parseBracketExpression(content) {
|
|
639
|
+
// Filter expression: ?(@.property == 'value')
|
|
640
|
+
if (content.startsWith('?(')) {
|
|
641
|
+
const filterExpr = content.substring(2, content.length - 1);
|
|
642
|
+
return { type: 'filter', expression: parseFilterExpression(filterExpr) };
|
|
643
|
+
}
|
|
644
|
+
// Array index: 0, 1, 2, etc.
|
|
645
|
+
const index = parseInt(content, 10);
|
|
646
|
+
if (!isNaN(index)) {
|
|
647
|
+
return { type: 'index', value: index };
|
|
648
|
+
}
|
|
649
|
+
throw new Error(`Unsupported bracket expression: ${content}`);
|
|
650
|
+
}
|
|
651
|
+
/**
|
|
652
|
+
* Parse filter expression like @.name == 'value'
|
|
653
|
+
*/
|
|
654
|
+
function parseFilterExpression(expr) {
|
|
655
|
+
const opIndex = expr.indexOf('==');
|
|
656
|
+
if (opIndex === -1) {
|
|
657
|
+
throw new Error(`Filter expression must use == operator: ${expr}`);
|
|
658
|
+
}
|
|
659
|
+
const left = expr.substring(0, opIndex).trim();
|
|
660
|
+
const right = expr.substring(opIndex + 2).trim();
|
|
661
|
+
// Parse left side (should be @.property)
|
|
662
|
+
if (!left.startsWith('@.')) {
|
|
663
|
+
throw new Error(`Filter expression must start with @.: ${expr}`);
|
|
664
|
+
}
|
|
665
|
+
const property = left.substring(2);
|
|
666
|
+
// Parse right side (value) using regex to extract quoted strings
|
|
667
|
+
const quotedMatch = right.match(/^(?<quote>['"])(?<content>.*?)\k<quote>$/);
|
|
668
|
+
if (!quotedMatch) {
|
|
669
|
+
throw new Error(`Filter expression value must be a quoted string: ${expr}`);
|
|
670
|
+
}
|
|
671
|
+
return { property, value: quotedMatch.groups['content'] };
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Apply a single token to the current value
|
|
675
|
+
*/
|
|
676
|
+
function applyToken(current, token) {
|
|
677
|
+
switch (token.type) {
|
|
678
|
+
case 'property':
|
|
679
|
+
return applyProperty(current, token.name);
|
|
680
|
+
case 'index':
|
|
681
|
+
return applyIndex(current, token.value);
|
|
682
|
+
case 'filter':
|
|
683
|
+
return applyFilter(current, token.expression);
|
|
684
|
+
default:
|
|
685
|
+
throw new Error(`Unsupported token type: ${token.type}`);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
/**
|
|
689
|
+
* Apply property access
|
|
690
|
+
*/
|
|
691
|
+
function applyProperty(current, property) {
|
|
692
|
+
if (!isObject(current) || !(property in current)) {
|
|
693
|
+
return undefined;
|
|
694
|
+
}
|
|
695
|
+
return current[property];
|
|
696
|
+
}
|
|
697
|
+
/**
|
|
698
|
+
* Apply array index access
|
|
699
|
+
*/
|
|
700
|
+
function applyIndex(current, index) {
|
|
701
|
+
if (!Array.isArray(current) || index < 0 || index >= current.length) {
|
|
702
|
+
return undefined;
|
|
703
|
+
}
|
|
704
|
+
return current[index];
|
|
705
|
+
}
|
|
706
|
+
/**
|
|
707
|
+
* Apply filter expression
|
|
708
|
+
*
|
|
709
|
+
* This function returns the first item that matches the filter expression.
|
|
710
|
+
*/
|
|
711
|
+
function applyFilter(current, filter) {
|
|
712
|
+
if (!Array.isArray(current)) {
|
|
713
|
+
return undefined;
|
|
714
|
+
}
|
|
715
|
+
for (const item of current) {
|
|
716
|
+
if (isObject(item) && matchesFilter(item, filter)) {
|
|
717
|
+
return item;
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
return undefined;
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Check if an item matches a filter expression
|
|
724
|
+
*/
|
|
725
|
+
function matchesFilter(item, filter) {
|
|
726
|
+
if (!(filter.property in item)) {
|
|
727
|
+
return false;
|
|
728
|
+
}
|
|
729
|
+
return item[filter.property] === filter.value;
|
|
730
|
+
}
|
|
731
|
+
|
|
566
732
|
exports.FieldValueTypes = FieldValueTypes;
|
|
567
733
|
exports.OperatorTypes = OperatorTypes;
|
|
568
734
|
exports.RelationSemantics = RelationSemantics;
|
|
569
735
|
exports.Semantics = Semantics;
|
|
570
736
|
exports.StatusCodes = StatusCodes;
|
|
571
737
|
exports.fieldTypeCompatibilityMatrix = fieldTypeCompatibilityMatrix;
|
|
738
|
+
exports.findRelationByJSONPath = findRelationByJSONPath;
|
|
572
739
|
exports.isFieldSchema = isFieldSchema;
|
|
573
740
|
exports.isFieldValueType = isFieldValueType;
|
|
574
741
|
exports.isItem = isItem;
|
|
575
742
|
exports.isItemSummary = isItemSummary;
|
|
743
|
+
exports.isObject = isObject;
|
|
576
744
|
exports.isReferenceRelation = isReferenceRelation;
|
|
577
745
|
exports.isRelation = isRelation;
|
|
578
746
|
exports.isRelationSchema = isRelationSchema;
|
|
579
747
|
exports.isRelationSchemaOrSelf = isRelationSchemaOrSelf;
|
|
580
748
|
exports.isRelationSummary = isRelationSummary;
|
|
581
749
|
exports.isSemantic = isSemantic;
|
|
750
|
+
exports.isString = isString;
|
|
751
|
+
exports.isUndefined = isUndefined;
|
package/dist/src/index.d.ts
CHANGED
package/dist/src/index.js
CHANGED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import * as Api from './index.js';
|
|
2
|
+
/**
|
|
3
|
+
* JSONPath parser that returns a relation that is guaranteed to have its schema populated.
|
|
4
|
+
*/
|
|
5
|
+
export declare function findRelationByJSONPath(item: Api.Item, query: string): Api.Relation | Api.RelationWithPopulatedSchema<Api.RelationSummary> | Api.RelationWithPopulatedSchema<Api.ReferenceRelation> | undefined;
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import * as Api from './index.js';
|
|
2
|
+
/**
|
|
3
|
+
* JSONPath parser that returns a relation that is guaranteed to have its schema populated.
|
|
4
|
+
*/
|
|
5
|
+
export function findRelationByJSONPath(item, query) {
|
|
6
|
+
const tokens = parseJSONPath(query);
|
|
7
|
+
const schemas = [];
|
|
8
|
+
let current = item;
|
|
9
|
+
for (const token of tokens) {
|
|
10
|
+
if (current === '__self') {
|
|
11
|
+
const previousSchema = schemas[schemas.length - 1];
|
|
12
|
+
if (!previousSchema) {
|
|
13
|
+
throw new Error(`Invalid use of __self`);
|
|
14
|
+
}
|
|
15
|
+
current = previousSchema;
|
|
16
|
+
}
|
|
17
|
+
const result = applyToken(current, token);
|
|
18
|
+
if (Api.isObject(result) && Api.isRelationSchema(result['schema'])) {
|
|
19
|
+
schemas.push(result['schema']);
|
|
20
|
+
}
|
|
21
|
+
current = result;
|
|
22
|
+
}
|
|
23
|
+
if (Api.isRelation(current)) {
|
|
24
|
+
return current;
|
|
25
|
+
}
|
|
26
|
+
if (Api.isReferenceRelation(current) || Api.isRelationSummary(current)) {
|
|
27
|
+
const latestSchema = schemas[schemas.length - 1];
|
|
28
|
+
if (latestSchema === undefined) {
|
|
29
|
+
throw new Error(`No schema found for relation ${current.label}`);
|
|
30
|
+
}
|
|
31
|
+
return { ...current, schema: latestSchema };
|
|
32
|
+
}
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Parse JSONPath expression into tokens
|
|
37
|
+
*/
|
|
38
|
+
function parseJSONPath(query) {
|
|
39
|
+
const tokens = [];
|
|
40
|
+
let remaining = query;
|
|
41
|
+
// Remove root $ if present
|
|
42
|
+
if (remaining.startsWith('$')) {
|
|
43
|
+
remaining = remaining.substring(1);
|
|
44
|
+
}
|
|
45
|
+
while (remaining.length > 0) {
|
|
46
|
+
// Skip leading dots
|
|
47
|
+
if (remaining.startsWith('.')) {
|
|
48
|
+
remaining = remaining.substring(1);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
// Parse bracket notation [...]
|
|
52
|
+
if (remaining.startsWith('[')) {
|
|
53
|
+
const bracketMatch = remaining.match(/^\[([^\]]*)\]/);
|
|
54
|
+
if (!bracketMatch) {
|
|
55
|
+
throw new Error(`Unclosed bracket in JSONPath: ${query}`);
|
|
56
|
+
}
|
|
57
|
+
remaining = remaining.substring(bracketMatch[0].length);
|
|
58
|
+
tokens.push(parseBracketExpression(String(bracketMatch[1])));
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
// Parse property name (until . or [ or end)
|
|
62
|
+
const propertyMatch = remaining.match(/^([^.[]+)/);
|
|
63
|
+
if (propertyMatch) {
|
|
64
|
+
const propertyName = String(propertyMatch[1]);
|
|
65
|
+
remaining = remaining.substring(propertyName.length);
|
|
66
|
+
tokens.push({ type: 'property', name: propertyName });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return tokens;
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* Parse bracket expression into a token
|
|
73
|
+
*/
|
|
74
|
+
function parseBracketExpression(content) {
|
|
75
|
+
// Filter expression: ?(@.property == 'value')
|
|
76
|
+
if (content.startsWith('?(')) {
|
|
77
|
+
const filterExpr = content.substring(2, content.length - 1);
|
|
78
|
+
return { type: 'filter', expression: parseFilterExpression(filterExpr) };
|
|
79
|
+
}
|
|
80
|
+
// Array index: 0, 1, 2, etc.
|
|
81
|
+
const index = parseInt(content, 10);
|
|
82
|
+
if (!isNaN(index)) {
|
|
83
|
+
return { type: 'index', value: index };
|
|
84
|
+
}
|
|
85
|
+
throw new Error(`Unsupported bracket expression: ${content}`);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Parse filter expression like @.name == 'value'
|
|
89
|
+
*/
|
|
90
|
+
function parseFilterExpression(expr) {
|
|
91
|
+
const opIndex = expr.indexOf('==');
|
|
92
|
+
if (opIndex === -1) {
|
|
93
|
+
throw new Error(`Filter expression must use == operator: ${expr}`);
|
|
94
|
+
}
|
|
95
|
+
const left = expr.substring(0, opIndex).trim();
|
|
96
|
+
const right = expr.substring(opIndex + 2).trim();
|
|
97
|
+
// Parse left side (should be @.property)
|
|
98
|
+
if (!left.startsWith('@.')) {
|
|
99
|
+
throw new Error(`Filter expression must start with @.: ${expr}`);
|
|
100
|
+
}
|
|
101
|
+
const property = left.substring(2);
|
|
102
|
+
// Parse right side (value) using regex to extract quoted strings
|
|
103
|
+
const quotedMatch = right.match(/^(?<quote>['"])(?<content>.*?)\k<quote>$/);
|
|
104
|
+
if (!quotedMatch) {
|
|
105
|
+
throw new Error(`Filter expression value must be a quoted string: ${expr}`);
|
|
106
|
+
}
|
|
107
|
+
return { property, value: quotedMatch.groups['content'] };
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Apply a single token to the current value
|
|
111
|
+
*/
|
|
112
|
+
function applyToken(current, token) {
|
|
113
|
+
switch (token.type) {
|
|
114
|
+
case 'property':
|
|
115
|
+
return applyProperty(current, token.name);
|
|
116
|
+
case 'index':
|
|
117
|
+
return applyIndex(current, token.value);
|
|
118
|
+
case 'filter':
|
|
119
|
+
return applyFilter(current, token.expression);
|
|
120
|
+
default:
|
|
121
|
+
throw new Error(`Unsupported token type: ${token.type}`);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Apply property access
|
|
126
|
+
*/
|
|
127
|
+
function applyProperty(current, property) {
|
|
128
|
+
if (!Api.isObject(current) || !(property in current)) {
|
|
129
|
+
return undefined;
|
|
130
|
+
}
|
|
131
|
+
return current[property];
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Apply array index access
|
|
135
|
+
*/
|
|
136
|
+
function applyIndex(current, index) {
|
|
137
|
+
if (!Array.isArray(current) || index < 0 || index >= current.length) {
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
return current[index];
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Apply filter expression
|
|
144
|
+
*
|
|
145
|
+
* This function returns the first item that matches the filter expression.
|
|
146
|
+
*/
|
|
147
|
+
function applyFilter(current, filter) {
|
|
148
|
+
if (!Array.isArray(current)) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
for (const item of current) {
|
|
152
|
+
if (Api.isObject(item) && matchesFilter(item, filter)) {
|
|
153
|
+
return item;
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return undefined;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Check if an item matches a filter expression
|
|
160
|
+
*/
|
|
161
|
+
function matchesFilter(item, filter) {
|
|
162
|
+
if (!(filter.property in item)) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
return item[filter.property] === filter.value;
|
|
166
|
+
}
|
package/dist/src/types.d.ts
CHANGED
|
@@ -413,6 +413,12 @@ export interface RelationSchema {
|
|
|
413
413
|
*/
|
|
414
414
|
relations?: RelationSummary[];
|
|
415
415
|
}
|
|
416
|
+
/**
|
|
417
|
+
* A type that guarantees the presence of a populated schema by preventing the use of __self.
|
|
418
|
+
*/
|
|
419
|
+
export type RelationWithPopulatedSchema<T extends RelationSummary | ReferenceRelation> = Omit<T, 'schema'> & {
|
|
420
|
+
schema: Exclude<RelationSchema, '__self'>;
|
|
421
|
+
};
|
|
416
422
|
/**
|
|
417
423
|
* A CreateItemRequestPayload describes the shape of a request on an item creation endpoint.
|
|
418
424
|
*/
|