Skip to content

Commit 7fd788b

Browse files
authored
Add support for derived properties (#2065)
1 parent e7bf02a commit 7fd788b

File tree

16 files changed

+1743
-113
lines changed

16 files changed

+1743
-113
lines changed

.changeset/ready-onions-join.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@osdk/client.unstable": patch
3+
"@osdk/maker": patch
4+
---
5+
6+
Support derived properties

packages/client.unstable/src/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,3 +72,9 @@ export type {
7272
OntologyIrValueTypeBlockData,
7373
OntologyIrValueTypeBlockDataEntry,
7474
} from "./blockDataIr.js";
75+
76+
export type {
77+
DerivedPropertyLinkTypeSide,
78+
OntologyIrDerivedPropertiesDefinition,
79+
OntologyIrDerivedPropertyAggregation,
80+
} from "./generated/ontology-metadata/api/derivedproperties/__components.js";

packages/maker/README.md

Lines changed: 86 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ The Maker package provides a type-safe, programmatic way to define ontologies, w
1515
- [Interface Link Constraints](#interface-link-constraints)
1616
- [Defining Actions](#defining-actions)
1717
- [Importing Ontology Entities](#importing-ontology-entities)
18+
- [Advanced](#advanced)
1819

1920
## Getting Started
2021

@@ -441,7 +442,7 @@ const customerObject = defineObject({
441442
});
442443
```
443444

444-
### Object with Datasource
445+
### Object with Custom Datasources
445446

446447
```typescript
447448
// Stream-backed object with retention period
@@ -456,27 +457,10 @@ const eventObject = defineObject({
456457
"eventName": { type: "string", displayName: "Event Name" },
457458
"timestamp": { type: "timestamp" },
458459
},
459-
datasource: {
460+
datasources: [{
460461
type: "stream",
461462
retentionPeriod: "P90D", // 90 days retention (ISO 8601 duration format)
462-
},
463-
});
464-
465-
// Dataset-backed object
466-
const productObject = defineObject({
467-
apiName: "product",
468-
displayName: "Product",
469-
pluralDisplayName: "Products",
470-
titlePropertyApiName: "name",
471-
primaryKeyPropertyApiName: "id",
472-
properties: {
473-
"id": { type: "string", displayName: "ID" },
474-
"name": { type: "string" },
475-
"price": { type: "decimal" },
476-
},
477-
datasource: {
478-
type: "dataset",
479-
},
463+
}],
480464
});
481465
```
482466

@@ -663,10 +647,12 @@ const modifyPersonAction = defineModifyInterfaceObjectAction({
663647
});
664648
```
665649

666-
### Custom Action
650+
## Advanced
651+
652+
### Custom Actions
667653

668654
More customization such as security/submission criteria, constraints on parameter values, parameter overrides, etc.
669-
can also be added.
655+
can be added to actions.
670656

671657
```typescript
672658
import {
@@ -741,3 +727,81 @@ const modifyObjectActionType = defineModifyObjectAction(
741727
},
742728
);
743729
```
730+
731+
### Derived Properties
732+
733+
Objects can have derived properties, which are computed at runtime from other linked objects. Properties can be mapped directly, or an aggregation function (e.g. `collectList`, `avg`, `max`, etc.) can be used.
734+
735+
```typescript
736+
const passenger = defineObject({
737+
displayName: "Passenger",
738+
pluralDisplayName: "Passengers",
739+
apiName: "passenger",
740+
primaryKeyPropertyApiName: "name",
741+
titlePropertyApiName: "name",
742+
properties: {
743+
name: {
744+
type: "string",
745+
displayName: "Name",
746+
},
747+
flight_id: {
748+
type: "string",
749+
displayName: "Flight ID",
750+
},
751+
},
752+
});
753+
const flightToPassengers = defineLink({
754+
apiName: "flightToPassengersLink",
755+
one: {
756+
// because the object has not been created yet,
757+
// reference it by its fully qualified API name manually
758+
object: "com.palantir.flight",
759+
metadata: {
760+
apiName: "flightFromPassengers",
761+
},
762+
},
763+
toMany: {
764+
object: passenger.apiName,
765+
metadata: {
766+
apiName: "passengersFromFlight",
767+
},
768+
},
769+
manyForeignKeyProperty: "flight_id",
770+
});
771+
const flight = defineObject({
772+
displayName: "Flight",
773+
pluralDisplayName: "Flights",
774+
apiName: "flight",
775+
primaryKeyPropertyApiName: "id",
776+
titlePropertyApiName: "id",
777+
properties: {
778+
id: {
779+
type: "string",
780+
displayName: "ID",
781+
},
782+
passengersList: {
783+
type: "string",
784+
array: true,
785+
displayName: "Passengers",
786+
},
787+
},
788+
datasources: [
789+
// the dataset will back all of the properties not specified in other datasources
790+
{ type: "dataset" },
791+
{
792+
type: "derived",
793+
// multi-hop link traversals are also supported, just extend this list!
794+
linkDefinition: [{
795+
linkType: flightToPassengers,
796+
}],
797+
propertyMapping: {
798+
passengersList: {
799+
type: "collectList",
800+
property: "name",
801+
limit: 100,
802+
},
803+
},
804+
},
805+
],
806+
});
807+
```

packages/maker/src/api/defineCreateObjectAction.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
validateActionParameters,
2828
validateParameterOrdering,
2929
} from "./defineAction.js";
30+
import { isStruct } from "./properties/PropertyTypeType.js";
3031

3132
export function defineCreateObjectAction(
3233
def: ActionTypeUserDefinition,
@@ -36,9 +37,16 @@ export function defineCreateObjectAction(
3637
Object.keys(def.objectType.properties ?? {}),
3738
def.objectType.apiName,
3839
);
40+
const propertiesWithDerivedDatasources = (def.objectType.datasources ?? [])
41+
.filter(ds => ds.type === "derived").flatMap(ds =>
42+
Object.keys(ds.propertyMapping)
43+
);
3944
const propertyParameters = Object.keys(def.objectType.properties ?? {})
4045
.filter(
41-
id => isPropertyParameter(def, id, def.objectType.properties?.[id].type!),
46+
id =>
47+
isPropertyParameter(def, id, def.objectType.properties?.[id].type!)
48+
&& !isStruct(def.objectType.properties?.[id].type!)
49+
&& !propertiesWithDerivedDatasources.includes(id),
4250
);
4351
const parameterNames = new Set(propertyParameters);
4452
Object.keys(def.parameterConfiguration ?? {}).forEach(param =>

packages/maker/src/api/defineCreateOrModifyObjectAction.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,18 @@ export function defineCreateOrModifyObjectAction(
3737
Object.keys(def.objectType.properties ?? {}),
3838
def.objectType.apiName,
3939
);
40+
const propertiesWithDerivedDatasources = (def.objectType.datasources ?? [])
41+
.filter(ds => ds.type === "derived").flatMap(ds =>
42+
Object.keys(ds.propertyMapping)
43+
);
4044
const propertyParameters = Object.keys(def.objectType.properties ?? {})
4145
.filter(
4246
id =>
4347
!Object.keys(def.nonParameterMappings ?? {}).includes(id)
4448
&& !def.excludedProperties?.includes(id)
4549
&& !isStruct(def.objectType.properties?.[id].type!)
46-
&& id !== def.objectType.primaryKeyPropertyApiName,
50+
&& id !== def.objectType.primaryKeyPropertyApiName
51+
&& !propertiesWithDerivedDatasources.includes(id),
4752
);
4853
const parameterNames = new Set(propertyParameters);
4954
Object.keys(def.parameterConfiguration ?? {}).forEach(param =>

packages/maker/src/api/defineLink.ts

Lines changed: 4 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,6 @@
1515
*/
1616

1717
import type { LinkTypeMetadata } from "@osdk/client.unstable";
18-
import invariant from "tiny-invariant";
1918
import { OntologyEntityTypeEnum } from "./common/OntologyEntityTypeEnum.js";
2019
import {
2120
convertToPluralDisplayName,
@@ -34,50 +33,13 @@ import type {
3433
OneToManyObjectLinkReferenceUserDefinition,
3534
} from "./links/LinkType.js";
3635

37-
const typeIdPattern = /([a-z][a-z0-9\\-]*)/;
38-
3936
export function defineLink(
4037
linkDefinition: LinkTypeDefinition,
4138
): LinkType {
42-
if ("one" in linkDefinition) {
43-
const foreignKey = linkDefinition.toMany.object.properties
44-
?.[linkDefinition.manyForeignKeyProperty];
45-
invariant(
46-
foreignKey !== undefined,
47-
`Foreign key ${linkDefinition.manyForeignKeyProperty} on link ${linkDefinition.apiName} does not exist on object ${linkDefinition.toMany.object.apiName}}`,
48-
);
49-
50-
invariant(
51-
typeIdPattern.test(linkDefinition.apiName),
52-
`Top level link api names are expected to match the regex pattern ([a-z][a-z0-9\\-]*) ${linkDefinition.apiName} does not match`,
53-
);
39+
// NOTE: we would normally do validation here, but because of circular dependencies
40+
// we have to wait to validate until everything has been defined. The code for validation
41+
// was moved to convertLink.ts.
5442

55-
const typesMatch = foreignKey.type
56-
=== linkDefinition.one.object.properties
57-
?.[linkDefinition.one.object.primaryKeyPropertyApiName].type;
58-
invariant(
59-
typesMatch,
60-
`Link ${linkDefinition.apiName} has type mismatch between the one side's primary key and the foreign key on the many side`,
61-
);
62-
}
63-
if ("intermediaryObjectType" in linkDefinition) {
64-
invariant(
65-
"one" in linkDefinition.many.linkToIntermediary
66-
&& linkDefinition.many.linkToIntermediary.one.object.apiName
67-
=== linkDefinition.many.object.apiName
68-
&& linkDefinition.many.linkToIntermediary.toMany.object.apiName
69-
=== linkDefinition.intermediaryObjectType.apiName,
70-
`LinkTypeA ${linkDefinition.many.linkToIntermediary.apiName} must be a many to one link from intermediary object ${linkDefinition.intermediaryObjectType.apiName} to objectA ${linkDefinition.many.object.apiName}`,
71-
);
72-
invariant(
73-
"one" in linkDefinition.toMany.linkToIntermediary
74-
&& linkDefinition.toMany.linkToIntermediary.one.object.apiName
75-
=== linkDefinition.toMany.object.apiName
76-
&& linkDefinition.toMany.linkToIntermediary.toMany.object.apiName
77-
=== linkDefinition.intermediaryObjectType.apiName,
78-
`LinkTypeB ${linkDefinition.toMany.linkToIntermediary.apiName} must be a many to one link from intermediary object ${linkDefinition.intermediaryObjectType.apiName} to objectB ${linkDefinition.toMany.object.apiName}`,
79-
);
80-
}
8143
let fullLinkDefinition;
8244
if ("one" in linkDefinition) {
8345
fullLinkDefinition = {
@@ -107,6 +69,7 @@ export function defineLink(
10769
? linkDefinition.cardinality
10870
: undefined,
10971
...fullLinkDefinition,
72+
apiName: linkDefinition.apiName,
11073
__type: OntologyEntityTypeEnum.LINK_TYPE,
11174
};
11275
updateOntology(linkType);

0 commit comments

Comments
 (0)