@stndrds/schema 0.1.0-alpha.58 → 0.1.0-alpha.60
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/{chunk-2OS63USQ.js → chunk-KN6UXXVU.js} +1086 -268
- package/dist/{chunk-JRY236AO.mjs → chunk-RRCPZUSK.mjs} +906 -88
- package/dist/index.d.mts +177 -6
- package/dist/index.d.ts +177 -6
- package/dist/index.js +42 -6
- package/dist/index.mjs +37 -1
- package/dist/{runtime-C4vB_EcQ.d.ts → runtime-Cx6QJ4QV.d.ts} +297 -46
- package/dist/{runtime-SCsDOQi0.d.mts → runtime-D0XMDY1D.d.mts} +297 -46
- package/dist/runtime.d.mts +2 -2
- package/dist/runtime.d.ts +2 -2
- package/dist/runtime.js +4 -2
- package/dist/runtime.mjs +3 -1
- package/dist/validation/validators.d.mts +1 -1
- package/dist/validation/validators.d.ts +1 -1
- package/dist/{validators-BgmWgr-G.d.mts → validators-BIAmz0CD.d.mts} +145 -2
- package/dist/{validators-miARInAq.d.ts → validators-BPIB7Miq.d.ts} +145 -2
- package/package.json +2 -2
|
@@ -307,8 +307,8 @@ var cacheKeys = {
|
|
|
307
307
|
searchResults: (tenantId, objectId, hash) => `search:${tenantId}:${objectId}:${hash}`,
|
|
308
308
|
/** All search results for an object (for invalidation) */
|
|
309
309
|
allSearchResults: (tenantId, objectId) => `search:${tenantId}:${objectId}:*`,
|
|
310
|
-
/** Global search results */
|
|
311
|
-
globalSearch: (tenantId, hash) => `gsearch:${tenantId}:${hash}`,
|
|
310
|
+
/** Global search results (3-param signature to match cachedList pattern) */
|
|
311
|
+
globalSearch: (tenantId, _id, hash) => `gsearch:${tenantId}:${hash}`,
|
|
312
312
|
/** All global search results for tenant (for invalidation) */
|
|
313
313
|
allGlobalSearch: (tenantId) => `gsearch:${tenantId}:*`,
|
|
314
314
|
// -------------------------------------------------------------------------
|
|
@@ -3949,7 +3949,6 @@ function createMockObjectRecordsRepository(stores) {
|
|
|
3949
3949
|
objectLabel: obj?.label ?? "Unknown",
|
|
3950
3950
|
label: renderLabelExpression(labelExpression, enrichedValues),
|
|
3951
3951
|
recordId: r.id,
|
|
3952
|
-
values: r.values,
|
|
3953
3952
|
completionStatus: r.completionStatus,
|
|
3954
3953
|
createdAt: r.createdAt,
|
|
3955
3954
|
updatedAt: r.updatedAt
|
|
@@ -3957,6 +3956,36 @@ function createMockObjectRecordsRepository(stores) {
|
|
|
3957
3956
|
});
|
|
3958
3957
|
return Promise.resolve({ results, total });
|
|
3959
3958
|
},
|
|
3959
|
+
globalSearchGrouped(query, options) {
|
|
3960
|
+
const limitPerGroup = options?.limitPerGroup ?? 5;
|
|
3961
|
+
return this.globalSearch(query, {
|
|
3962
|
+
objectNames: options?.objectNames,
|
|
3963
|
+
limit: 500,
|
|
3964
|
+
offset: 0
|
|
3965
|
+
}).then(({ results }) => {
|
|
3966
|
+
const groupMap = /* @__PURE__ */ new Map();
|
|
3967
|
+
for (const result of results) {
|
|
3968
|
+
let group2 = groupMap.get(result.objectName);
|
|
3969
|
+
if (!group2) {
|
|
3970
|
+
group2 = {
|
|
3971
|
+
objectName: result.objectName,
|
|
3972
|
+
objectLabel: result.objectLabel,
|
|
3973
|
+
results: [],
|
|
3974
|
+
totalInGroup: 0
|
|
3975
|
+
};
|
|
3976
|
+
groupMap.set(result.objectName, group2);
|
|
3977
|
+
}
|
|
3978
|
+
group2.totalInGroup++;
|
|
3979
|
+
if (group2.results.length < limitPerGroup) {
|
|
3980
|
+
group2.results.push(result);
|
|
3981
|
+
}
|
|
3982
|
+
}
|
|
3983
|
+
const groups = Array.from(groupMap.values());
|
|
3984
|
+
groups.sort((a, b) => b.totalInGroup - a.totalInGroup);
|
|
3985
|
+
const total = groups.reduce((sum, g) => sum + g.totalInGroup, 0);
|
|
3986
|
+
return { groups, total };
|
|
3987
|
+
});
|
|
3988
|
+
},
|
|
3960
3989
|
// -------------------------------------------------------------------------
|
|
3961
3990
|
// Schema Integrity Methods
|
|
3962
3991
|
// -------------------------------------------------------------------------
|
|
@@ -4059,6 +4088,91 @@ function createMockObjectRecordsRepository(stores) {
|
|
|
4059
4088
|
};
|
|
4060
4089
|
}
|
|
4061
4090
|
|
|
4091
|
+
// src/runtime/mock/mock-relation-attributes.ts
|
|
4092
|
+
import { randomUUID } from "crypto";
|
|
4093
|
+
function createMockRelationAttributesRepository(stores) {
|
|
4094
|
+
return {
|
|
4095
|
+
async upsertBatch(items) {
|
|
4096
|
+
const context = getContext2();
|
|
4097
|
+
if (!context) {
|
|
4098
|
+
throw new Error("Context required for relationAttributes operations");
|
|
4099
|
+
}
|
|
4100
|
+
const results = [];
|
|
4101
|
+
for (const item of items) {
|
|
4102
|
+
const _key = `${context.tenantId}:${item.fromObject}:${item.fromId}:${item.fromAttribute}:${item.toId}`;
|
|
4103
|
+
const existing = Array.from(stores.relationAttributes.values()).find(
|
|
4104
|
+
(row) => row.tenantId === context.tenantId && row.fromObject === item.fromObject && row.fromId === item.fromId && row.fromAttribute === item.fromAttribute && row.toId === item.toId
|
|
4105
|
+
);
|
|
4106
|
+
if (existing) {
|
|
4107
|
+
existing.properties = item.properties ?? {};
|
|
4108
|
+
existing.updatedAt = /* @__PURE__ */ new Date();
|
|
4109
|
+
existing.updatedBy = item.updatedBy ?? context.userId ?? null;
|
|
4110
|
+
results.push(existing);
|
|
4111
|
+
} else {
|
|
4112
|
+
const row = {
|
|
4113
|
+
id: randomUUID(),
|
|
4114
|
+
tenantId: context.tenantId,
|
|
4115
|
+
fromObject: item.fromObject,
|
|
4116
|
+
fromId: item.fromId,
|
|
4117
|
+
fromAttribute: item.fromAttribute,
|
|
4118
|
+
toId: item.toId,
|
|
4119
|
+
properties: item.properties ?? {},
|
|
4120
|
+
createdAt: /* @__PURE__ */ new Date(),
|
|
4121
|
+
updatedAt: /* @__PURE__ */ new Date(),
|
|
4122
|
+
createdBy: item.createdBy ?? context.userId ?? null,
|
|
4123
|
+
updatedBy: item.updatedBy ?? context.userId ?? null
|
|
4124
|
+
};
|
|
4125
|
+
stores.relationAttributes.set(row.id, row);
|
|
4126
|
+
results.push(row);
|
|
4127
|
+
}
|
|
4128
|
+
}
|
|
4129
|
+
return results;
|
|
4130
|
+
},
|
|
4131
|
+
async findBySource(fromObject, fromId, fromAttribute) {
|
|
4132
|
+
const context = getContext2();
|
|
4133
|
+
if (!context) {
|
|
4134
|
+
throw new Error("Context required for relationAttributes operations");
|
|
4135
|
+
}
|
|
4136
|
+
return Array.from(stores.relationAttributes.values()).filter(
|
|
4137
|
+
(row) => row.tenantId === context.tenantId && row.fromObject === fromObject && row.fromId === fromId && row.fromAttribute === fromAttribute
|
|
4138
|
+
);
|
|
4139
|
+
},
|
|
4140
|
+
async findByTarget(toId) {
|
|
4141
|
+
const context = getContext2();
|
|
4142
|
+
if (!context) {
|
|
4143
|
+
throw new Error("Context required for relationAttributes operations");
|
|
4144
|
+
}
|
|
4145
|
+
return Array.from(stores.relationAttributes.values()).filter(
|
|
4146
|
+
(row) => row.tenantId === context.tenantId && row.toId === toId
|
|
4147
|
+
);
|
|
4148
|
+
},
|
|
4149
|
+
async deleteBySource(fromObject, fromId, fromAttribute) {
|
|
4150
|
+
const context = getContext2();
|
|
4151
|
+
if (!context) {
|
|
4152
|
+
throw new Error("Context required for relationAttributes operations");
|
|
4153
|
+
}
|
|
4154
|
+
const toDelete = Array.from(stores.relationAttributes.entries()).filter(
|
|
4155
|
+
([_id, row]) => row.tenantId === context.tenantId && row.fromObject === fromObject && row.fromId === fromId && row.fromAttribute === fromAttribute
|
|
4156
|
+
);
|
|
4157
|
+
for (const [id] of toDelete) {
|
|
4158
|
+
stores.relationAttributes.delete(id);
|
|
4159
|
+
}
|
|
4160
|
+
},
|
|
4161
|
+
async deleteByTarget(toId) {
|
|
4162
|
+
const context = getContext2();
|
|
4163
|
+
if (!context) {
|
|
4164
|
+
throw new Error("Context required for relationAttributes operations");
|
|
4165
|
+
}
|
|
4166
|
+
const toDelete = Array.from(stores.relationAttributes.entries()).filter(
|
|
4167
|
+
([_id, row]) => row.tenantId === context.tenantId && row.toId === toId
|
|
4168
|
+
);
|
|
4169
|
+
for (const [id] of toDelete) {
|
|
4170
|
+
stores.relationAttributes.delete(id);
|
|
4171
|
+
}
|
|
4172
|
+
}
|
|
4173
|
+
};
|
|
4174
|
+
}
|
|
4175
|
+
|
|
4062
4176
|
// src/runtime/mock/mock-stores.ts
|
|
4063
4177
|
function createEmptyStores() {
|
|
4064
4178
|
return {
|
|
@@ -4079,7 +4193,8 @@ function createEmptyStores() {
|
|
|
4079
4193
|
aiConversations: /* @__PURE__ */ new Map(),
|
|
4080
4194
|
aiMessages: /* @__PURE__ */ new Map(),
|
|
4081
4195
|
aiUserMemory: /* @__PURE__ */ new Map(),
|
|
4082
|
-
aiUsageMetrics: /* @__PURE__ */ new Map()
|
|
4196
|
+
aiUsageMetrics: /* @__PURE__ */ new Map(),
|
|
4197
|
+
relationAttributes: /* @__PURE__ */ new Map()
|
|
4083
4198
|
};
|
|
4084
4199
|
}
|
|
4085
4200
|
|
|
@@ -4994,6 +5109,8 @@ function createMockAdapter() {
|
|
|
4994
5109
|
aiConversations: createMockAIConversationsRepository(stores),
|
|
4995
5110
|
aiUserMemory: createMockAIUserMemoryRepository(stores),
|
|
4996
5111
|
aiUsageMetrics: createMockAIUsageMetricsRepository(stores),
|
|
5112
|
+
// Relation attributes repository
|
|
5113
|
+
relationAttributes: createMockRelationAttributesRepository(stores),
|
|
4997
5114
|
async transaction(callback) {
|
|
4998
5115
|
return await callback(adapter);
|
|
4999
5116
|
},
|
|
@@ -5019,6 +5136,7 @@ function createMockAdapter() {
|
|
|
5019
5136
|
stores.aiMessages.clear();
|
|
5020
5137
|
stores.aiUserMemory.clear();
|
|
5021
5138
|
stores.aiUsageMetrics.clear();
|
|
5139
|
+
stores.relationAttributes.clear();
|
|
5022
5140
|
}
|
|
5023
5141
|
};
|
|
5024
5142
|
return adapter;
|
|
@@ -5497,6 +5615,292 @@ function validateOptions(options, attributeName) {
|
|
|
5497
5615
|
}
|
|
5498
5616
|
}
|
|
5499
5617
|
|
|
5618
|
+
// src/types/relation-properties.ts
|
|
5619
|
+
var FORBIDDEN_PROPERTY_TYPES = [
|
|
5620
|
+
"formula",
|
|
5621
|
+
"rollup",
|
|
5622
|
+
"relation",
|
|
5623
|
+
"file",
|
|
5624
|
+
"user",
|
|
5625
|
+
"document",
|
|
5626
|
+
"richtext"
|
|
5627
|
+
];
|
|
5628
|
+
|
|
5629
|
+
// src/builders/property-schema-builder.ts
|
|
5630
|
+
var PropertySchemaBuilder = class {
|
|
5631
|
+
constructor() {
|
|
5632
|
+
this.definitions = [];
|
|
5633
|
+
}
|
|
5634
|
+
/**
|
|
5635
|
+
* Add a property to the schema
|
|
5636
|
+
* The type of property is automatically detected based on the builder methods used
|
|
5637
|
+
*/
|
|
5638
|
+
add(name, configure) {
|
|
5639
|
+
const builder = new PropertyTypeBuilder(name);
|
|
5640
|
+
const configured = configure(builder);
|
|
5641
|
+
const definition = configured.build();
|
|
5642
|
+
this.definitions.push(definition);
|
|
5643
|
+
return this;
|
|
5644
|
+
}
|
|
5645
|
+
/**
|
|
5646
|
+
* Build the final PropertySchema
|
|
5647
|
+
*/
|
|
5648
|
+
build() {
|
|
5649
|
+
return {
|
|
5650
|
+
definitions: this.definitions
|
|
5651
|
+
};
|
|
5652
|
+
}
|
|
5653
|
+
};
|
|
5654
|
+
var PropertyTypeBuilder = class {
|
|
5655
|
+
constructor(name) {
|
|
5656
|
+
this.name = name;
|
|
5657
|
+
}
|
|
5658
|
+
// Explicit type constructors
|
|
5659
|
+
text() {
|
|
5660
|
+
return new TextPropertyBuilder(this.name);
|
|
5661
|
+
}
|
|
5662
|
+
textarea() {
|
|
5663
|
+
return new TextareaPropertyBuilder(this.name);
|
|
5664
|
+
}
|
|
5665
|
+
number() {
|
|
5666
|
+
return new NumberPropertyBuilder(this.name);
|
|
5667
|
+
}
|
|
5668
|
+
checkbox() {
|
|
5669
|
+
return new CheckboxPropertyBuilder(this.name);
|
|
5670
|
+
}
|
|
5671
|
+
date() {
|
|
5672
|
+
return new DatePropertyBuilder(this.name);
|
|
5673
|
+
}
|
|
5674
|
+
phone() {
|
|
5675
|
+
return new PhonePropertyBuilder(this.name);
|
|
5676
|
+
}
|
|
5677
|
+
currency() {
|
|
5678
|
+
return new CurrencyPropertyBuilder(this.name);
|
|
5679
|
+
}
|
|
5680
|
+
status() {
|
|
5681
|
+
return new StatusPropertyBuilder(this.name);
|
|
5682
|
+
}
|
|
5683
|
+
select() {
|
|
5684
|
+
return new SelectPropertyBuilder(this.name);
|
|
5685
|
+
}
|
|
5686
|
+
multiselect() {
|
|
5687
|
+
return new MultiselectPropertyBuilder(this.name);
|
|
5688
|
+
}
|
|
5689
|
+
rating() {
|
|
5690
|
+
return new RatingPropertyBuilder(this.name);
|
|
5691
|
+
}
|
|
5692
|
+
location() {
|
|
5693
|
+
return new LocationPropertyBuilder(this.name);
|
|
5694
|
+
}
|
|
5695
|
+
};
|
|
5696
|
+
var BasePropertyBuilder = class {
|
|
5697
|
+
constructor(name) {
|
|
5698
|
+
this.definition = { name };
|
|
5699
|
+
}
|
|
5700
|
+
/**
|
|
5701
|
+
* Set the label
|
|
5702
|
+
*/
|
|
5703
|
+
label(label) {
|
|
5704
|
+
this.definition.label = label;
|
|
5705
|
+
return this;
|
|
5706
|
+
}
|
|
5707
|
+
/**
|
|
5708
|
+
* Mark as required
|
|
5709
|
+
*/
|
|
5710
|
+
required() {
|
|
5711
|
+
this.definition.required = true;
|
|
5712
|
+
return this;
|
|
5713
|
+
}
|
|
5714
|
+
/**
|
|
5715
|
+
* Set description
|
|
5716
|
+
*/
|
|
5717
|
+
description(description) {
|
|
5718
|
+
this.definition.description = description;
|
|
5719
|
+
return this;
|
|
5720
|
+
}
|
|
5721
|
+
/**
|
|
5722
|
+
* Build the final definition
|
|
5723
|
+
*/
|
|
5724
|
+
build() {
|
|
5725
|
+
return this.definition;
|
|
5726
|
+
}
|
|
5727
|
+
};
|
|
5728
|
+
var TextPropertyBuilder = class extends BasePropertyBuilder {
|
|
5729
|
+
constructor(name) {
|
|
5730
|
+
super(name);
|
|
5731
|
+
this.definition.type = "text";
|
|
5732
|
+
}
|
|
5733
|
+
minLength(value) {
|
|
5734
|
+
this.definition.minLength = value;
|
|
5735
|
+
return this;
|
|
5736
|
+
}
|
|
5737
|
+
maxLength(value) {
|
|
5738
|
+
this.definition.maxLength = value;
|
|
5739
|
+
return this;
|
|
5740
|
+
}
|
|
5741
|
+
pattern(pattern) {
|
|
5742
|
+
this.definition.pattern = pattern;
|
|
5743
|
+
return this;
|
|
5744
|
+
}
|
|
5745
|
+
placeholder(value) {
|
|
5746
|
+
this.definition.placeholder = value;
|
|
5747
|
+
return this;
|
|
5748
|
+
}
|
|
5749
|
+
};
|
|
5750
|
+
var TextareaPropertyBuilder = class extends BasePropertyBuilder {
|
|
5751
|
+
constructor(name) {
|
|
5752
|
+
super(name);
|
|
5753
|
+
this.definition.type = "textarea";
|
|
5754
|
+
}
|
|
5755
|
+
minLength(value) {
|
|
5756
|
+
this.definition.minLength = value;
|
|
5757
|
+
return this;
|
|
5758
|
+
}
|
|
5759
|
+
maxLength(value) {
|
|
5760
|
+
this.definition.maxLength = value;
|
|
5761
|
+
return this;
|
|
5762
|
+
}
|
|
5763
|
+
placeholder(value) {
|
|
5764
|
+
this.definition.placeholder = value;
|
|
5765
|
+
return this;
|
|
5766
|
+
}
|
|
5767
|
+
};
|
|
5768
|
+
var NumberPropertyBuilder = class extends BasePropertyBuilder {
|
|
5769
|
+
constructor(name) {
|
|
5770
|
+
super(name);
|
|
5771
|
+
this.definition.type = "number";
|
|
5772
|
+
}
|
|
5773
|
+
min(value) {
|
|
5774
|
+
this.definition.min = value;
|
|
5775
|
+
return this;
|
|
5776
|
+
}
|
|
5777
|
+
max(value) {
|
|
5778
|
+
this.definition.max = value;
|
|
5779
|
+
return this;
|
|
5780
|
+
}
|
|
5781
|
+
decimal(places) {
|
|
5782
|
+
this.definition.decimal = places;
|
|
5783
|
+
return this;
|
|
5784
|
+
}
|
|
5785
|
+
integer() {
|
|
5786
|
+
this.definition.integer = true;
|
|
5787
|
+
return this;
|
|
5788
|
+
}
|
|
5789
|
+
placeholder(value) {
|
|
5790
|
+
this.definition.placeholder = value;
|
|
5791
|
+
return this;
|
|
5792
|
+
}
|
|
5793
|
+
};
|
|
5794
|
+
var CheckboxPropertyBuilder = class extends BasePropertyBuilder {
|
|
5795
|
+
constructor(name) {
|
|
5796
|
+
super(name);
|
|
5797
|
+
this.definition.type = "checkbox";
|
|
5798
|
+
}
|
|
5799
|
+
};
|
|
5800
|
+
var DatePropertyBuilder = class extends BasePropertyBuilder {
|
|
5801
|
+
constructor(name) {
|
|
5802
|
+
super(name);
|
|
5803
|
+
this.definition.type = "date";
|
|
5804
|
+
}
|
|
5805
|
+
includeTime() {
|
|
5806
|
+
this.definition.includeTime = true;
|
|
5807
|
+
return this;
|
|
5808
|
+
}
|
|
5809
|
+
min(date2) {
|
|
5810
|
+
this.definition.min = date2;
|
|
5811
|
+
return this;
|
|
5812
|
+
}
|
|
5813
|
+
max(date2) {
|
|
5814
|
+
this.definition.max = date2;
|
|
5815
|
+
return this;
|
|
5816
|
+
}
|
|
5817
|
+
};
|
|
5818
|
+
var PhonePropertyBuilder = class extends BasePropertyBuilder {
|
|
5819
|
+
constructor(name) {
|
|
5820
|
+
super(name);
|
|
5821
|
+
this.definition.type = "phone";
|
|
5822
|
+
}
|
|
5823
|
+
};
|
|
5824
|
+
var CurrencyPropertyBuilder = class extends BasePropertyBuilder {
|
|
5825
|
+
constructor(name) {
|
|
5826
|
+
super(name);
|
|
5827
|
+
this.definition.type = "currency";
|
|
5828
|
+
}
|
|
5829
|
+
currency(code) {
|
|
5830
|
+
this.definition.currency = code;
|
|
5831
|
+
return this;
|
|
5832
|
+
}
|
|
5833
|
+
min(value) {
|
|
5834
|
+
this.definition.min = value;
|
|
5835
|
+
return this;
|
|
5836
|
+
}
|
|
5837
|
+
max(value) {
|
|
5838
|
+
this.definition.max = value;
|
|
5839
|
+
return this;
|
|
5840
|
+
}
|
|
5841
|
+
};
|
|
5842
|
+
var StatusPropertyBuilder = class extends BasePropertyBuilder {
|
|
5843
|
+
constructor(name) {
|
|
5844
|
+
super(name);
|
|
5845
|
+
this.definition.type = "status";
|
|
5846
|
+
}
|
|
5847
|
+
options(options) {
|
|
5848
|
+
this.definition.options = options;
|
|
5849
|
+
return this;
|
|
5850
|
+
}
|
|
5851
|
+
};
|
|
5852
|
+
var SelectPropertyBuilder = class extends BasePropertyBuilder {
|
|
5853
|
+
constructor(name) {
|
|
5854
|
+
super(name);
|
|
5855
|
+
this.definition.type = "select";
|
|
5856
|
+
}
|
|
5857
|
+
options(options) {
|
|
5858
|
+
this.definition.options = options;
|
|
5859
|
+
return this;
|
|
5860
|
+
}
|
|
5861
|
+
};
|
|
5862
|
+
var MultiselectPropertyBuilder = class extends BasePropertyBuilder {
|
|
5863
|
+
constructor(name) {
|
|
5864
|
+
super(name);
|
|
5865
|
+
this.definition.type = "multiselect";
|
|
5866
|
+
}
|
|
5867
|
+
options(options) {
|
|
5868
|
+
this.definition.options = options;
|
|
5869
|
+
return this;
|
|
5870
|
+
}
|
|
5871
|
+
maxSelections(value) {
|
|
5872
|
+
this.definition.maxSelections = value;
|
|
5873
|
+
return this;
|
|
5874
|
+
}
|
|
5875
|
+
};
|
|
5876
|
+
var RatingPropertyBuilder = class extends BasePropertyBuilder {
|
|
5877
|
+
constructor(name) {
|
|
5878
|
+
super(name);
|
|
5879
|
+
this.definition.type = "rating";
|
|
5880
|
+
}
|
|
5881
|
+
max(value) {
|
|
5882
|
+
this.definition.max = value;
|
|
5883
|
+
return this;
|
|
5884
|
+
}
|
|
5885
|
+
icon(icon) {
|
|
5886
|
+
this.definition.icon = icon;
|
|
5887
|
+
return this;
|
|
5888
|
+
}
|
|
5889
|
+
};
|
|
5890
|
+
var LocationPropertyBuilder = class extends BasePropertyBuilder {
|
|
5891
|
+
constructor(name) {
|
|
5892
|
+
super(name);
|
|
5893
|
+
this.definition.type = "location";
|
|
5894
|
+
}
|
|
5895
|
+
};
|
|
5896
|
+
function validatePropertyType(type) {
|
|
5897
|
+
if (FORBIDDEN_PROPERTY_TYPES.includes(type)) {
|
|
5898
|
+
throw new Error(
|
|
5899
|
+
`Property type "${type}" is not supported in .qualifyWith(). Only simple types (text, number, date, select, etc.) are allowed. Complex types (formula, rollup, relation, file, user, document) would require duplicating backend behavior.`
|
|
5900
|
+
);
|
|
5901
|
+
}
|
|
5902
|
+
}
|
|
5903
|
+
|
|
5500
5904
|
// src/builders/attribute-builders.ts
|
|
5501
5905
|
var BaseAttributeBuilder = class {
|
|
5502
5906
|
constructor(type, name, label) {
|
|
@@ -6053,6 +6457,32 @@ var SingleRelationAttributeBuilder = class extends BaseAttributeBuilder {
|
|
|
6053
6457
|
);
|
|
6054
6458
|
return multiBuilder;
|
|
6055
6459
|
}
|
|
6460
|
+
/**
|
|
6461
|
+
* Add properties to qualify the relation
|
|
6462
|
+
* Must be called AFTER .to() to ensure targets are defined
|
|
6463
|
+
*
|
|
6464
|
+
* @example
|
|
6465
|
+
* ```typescript
|
|
6466
|
+
* relation({ name: "mainCompany", label: "Main Company" })
|
|
6467
|
+
* .to("companies")
|
|
6468
|
+
* .qualifyWith(props => props
|
|
6469
|
+
* .add("role", select => select.options([...]).required())
|
|
6470
|
+
* .add("shares", number => number.min(0))
|
|
6471
|
+
* )
|
|
6472
|
+
* ```
|
|
6473
|
+
*/
|
|
6474
|
+
qualifyWith(configure) {
|
|
6475
|
+
const targets = this.attr.targets;
|
|
6476
|
+
if (!targets || targets.length === 0) {
|
|
6477
|
+
throw new Error(
|
|
6478
|
+
'.qualifyWith() must be called AFTER .to(). Example: relation({ name: "mainCompany" }).to("companies").qualifyWith(...)'
|
|
6479
|
+
);
|
|
6480
|
+
}
|
|
6481
|
+
const builder = new PropertySchemaBuilder();
|
|
6482
|
+
const schema = configure(builder).build();
|
|
6483
|
+
this.attr.properties = schema;
|
|
6484
|
+
return this;
|
|
6485
|
+
}
|
|
6056
6486
|
required() {
|
|
6057
6487
|
this.setRequired(true);
|
|
6058
6488
|
return this;
|
|
@@ -6114,6 +6544,33 @@ var MultiRelationAttributeBuilder = class extends BaseAttributeBuilder {
|
|
|
6114
6544
|
this.attr.maxItems = count;
|
|
6115
6545
|
return this;
|
|
6116
6546
|
}
|
|
6547
|
+
/**
|
|
6548
|
+
* Add properties to qualify the relation
|
|
6549
|
+
* Must be called AFTER .to() or .many() to ensure targets are defined
|
|
6550
|
+
*
|
|
6551
|
+
* @example
|
|
6552
|
+
* ```typescript
|
|
6553
|
+
* relation({ name: "companies", label: "Companies" })
|
|
6554
|
+
* .to("companies")
|
|
6555
|
+
* .many()
|
|
6556
|
+
* .qualifyWith(props => props
|
|
6557
|
+
* .add("role", select => select.options([...]).required())
|
|
6558
|
+
* .add("shares", number => number.min(0))
|
|
6559
|
+
* )
|
|
6560
|
+
* ```
|
|
6561
|
+
*/
|
|
6562
|
+
qualifyWith(configure) {
|
|
6563
|
+
const targets = this.attr.targets;
|
|
6564
|
+
if (!targets || targets.length === 0) {
|
|
6565
|
+
throw new Error(
|
|
6566
|
+
'.qualifyWith() must be called AFTER .to() or .many(). Example: relation({ name: "companies" }).to("companies").many().qualifyWith(...)'
|
|
6567
|
+
);
|
|
6568
|
+
}
|
|
6569
|
+
const builder = new PropertySchemaBuilder();
|
|
6570
|
+
const schema = configure(builder).build();
|
|
6571
|
+
this.attr.properties = schema;
|
|
6572
|
+
return this;
|
|
6573
|
+
}
|
|
6117
6574
|
required() {
|
|
6118
6575
|
this.setRequired(true);
|
|
6119
6576
|
return this;
|
|
@@ -6499,7 +6956,7 @@ function object(config) {
|
|
|
6499
6956
|
}
|
|
6500
6957
|
|
|
6501
6958
|
// src/builders/view-builder.ts
|
|
6502
|
-
import { randomUUID } from "crypto";
|
|
6959
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
6503
6960
|
import { z as z3 } from "zod";
|
|
6504
6961
|
var GroupBuilder = class {
|
|
6505
6962
|
constructor(id, label) {
|
|
@@ -7359,7 +7816,7 @@ var ListViewBuilder = class {
|
|
|
7359
7816
|
columns: this.data.columns,
|
|
7360
7817
|
columnSizing: this.data.columnSizing,
|
|
7361
7818
|
defaultFilters: this.data.defaultFilters ? {
|
|
7362
|
-
id:
|
|
7819
|
+
id: randomUUID2(),
|
|
7363
7820
|
combinator: this.data.defaultFilters.combinator,
|
|
7364
7821
|
rules: this.data.defaultFilters.rules
|
|
7365
7822
|
} : void 0,
|
|
@@ -7370,7 +7827,7 @@ var ListViewBuilder = class {
|
|
|
7370
7827
|
label: tab.label,
|
|
7371
7828
|
icon: tab.icon,
|
|
7372
7829
|
filters: tab.filters ? {
|
|
7373
|
-
id:
|
|
7830
|
+
id: randomUUID2(),
|
|
7374
7831
|
combinator: tab.filters.combinator,
|
|
7375
7832
|
rules: tab.filters.rules
|
|
7376
7833
|
} : void 0,
|
|
@@ -9799,6 +10256,13 @@ var RecordQueryService = class extends BaseService {
|
|
|
9799
10256
|
effectiveTotal = exhausted ? collected.length : Math.max(collected.length, result.total);
|
|
9800
10257
|
filteredRecords = collected.slice(requestedOffset, requestedOffset + requestedLimit);
|
|
9801
10258
|
}
|
|
10259
|
+
if (options?.include && options.include.length > 0) {
|
|
10260
|
+
filteredRecords = await this.includeRelationsWithProperties(
|
|
10261
|
+
filteredRecords,
|
|
10262
|
+
schema,
|
|
10263
|
+
options.include
|
|
10264
|
+
);
|
|
10265
|
+
}
|
|
9802
10266
|
if (!options?.skipFormulas) {
|
|
9803
10267
|
return {
|
|
9804
10268
|
records: enrichRecordsWithFormulas(filteredRecords, schema),
|
|
@@ -9874,6 +10338,79 @@ var RecordQueryService = class extends BaseService {
|
|
|
9874
10338
|
}
|
|
9875
10339
|
return result;
|
|
9876
10340
|
}
|
|
10341
|
+
// ============================================================================
|
|
10342
|
+
// INCLUDE RELATIONS WITH PROPERTIES
|
|
10343
|
+
// ============================================================================
|
|
10344
|
+
/**
|
|
10345
|
+
* Include relation properties in records.
|
|
10346
|
+
*
|
|
10347
|
+
* For each requested relation attribute:
|
|
10348
|
+
* - If attribute has properties → Fetch from relation_attributes and return hybrid format
|
|
10349
|
+
* - If attribute has NO properties → Return legacy format (string[] or string)
|
|
10350
|
+
*
|
|
10351
|
+
* Uses batch loading to avoid N+1 queries.
|
|
10352
|
+
*
|
|
10353
|
+
* @param records - Records to enrich with relation properties
|
|
10354
|
+
* @param schema - Object schema
|
|
10355
|
+
* @param includes - Array of relation attribute names to include
|
|
10356
|
+
* @returns Records enriched with relation properties in hybrid format
|
|
10357
|
+
* @private
|
|
10358
|
+
*/
|
|
10359
|
+
async includeRelationsWithProperties(records, schema, includes) {
|
|
10360
|
+
if (records.length === 0 || includes.length === 0) {
|
|
10361
|
+
return records;
|
|
10362
|
+
}
|
|
10363
|
+
for (const includeName of includes) {
|
|
10364
|
+
const attr = schema.attributes.find((a) => a.name === includeName);
|
|
10365
|
+
if (!attr || attr.type !== "relation") {
|
|
10366
|
+
continue;
|
|
10367
|
+
}
|
|
10368
|
+
if (attr.properties && this.adapter.relationAttributes) {
|
|
10369
|
+
const recordIds = records.map((r) => r.id);
|
|
10370
|
+
const relationAttributesRepo = this.adapter.relationAttributes;
|
|
10371
|
+
const allRelationProps = await Promise.all(
|
|
10372
|
+
recordIds.map(
|
|
10373
|
+
(recordId) => relationAttributesRepo.findBySource(schema.name, recordId, includeName)
|
|
10374
|
+
)
|
|
10375
|
+
);
|
|
10376
|
+
const propsByRecord = /* @__PURE__ */ new Map();
|
|
10377
|
+
allRelationProps.forEach((props, index) => {
|
|
10378
|
+
const recordId = recordIds[index];
|
|
10379
|
+
const propsMap = /* @__PURE__ */ new Map();
|
|
10380
|
+
for (const prop of props) {
|
|
10381
|
+
propsMap.set(prop.toId, prop.properties);
|
|
10382
|
+
}
|
|
10383
|
+
propsByRecord.set(recordId, propsMap);
|
|
10384
|
+
});
|
|
10385
|
+
for (const record of records) {
|
|
10386
|
+
const currentValue = record.values[includeName];
|
|
10387
|
+
const propsMap = propsByRecord.get(record.id);
|
|
10388
|
+
if (!currentValue) {
|
|
10389
|
+
continue;
|
|
10390
|
+
}
|
|
10391
|
+
if (!propsMap) {
|
|
10392
|
+
continue;
|
|
10393
|
+
}
|
|
10394
|
+
if (attr.cardinality === "many" && Array.isArray(currentValue)) {
|
|
10395
|
+
record.values[includeName] = currentValue.map((id) => {
|
|
10396
|
+
if (typeof id === "string") {
|
|
10397
|
+
const props = propsMap.get(id);
|
|
10398
|
+
return props ? { id, props } : { id };
|
|
10399
|
+
}
|
|
10400
|
+
return id;
|
|
10401
|
+
});
|
|
10402
|
+
} else if (attr.cardinality === "one") {
|
|
10403
|
+
const id = typeof currentValue === "string" ? currentValue : null;
|
|
10404
|
+
if (id) {
|
|
10405
|
+
const props = propsMap.get(id);
|
|
10406
|
+
record.values[includeName] = props ? { id, props } : { id };
|
|
10407
|
+
}
|
|
10408
|
+
}
|
|
10409
|
+
}
|
|
10410
|
+
}
|
|
10411
|
+
}
|
|
10412
|
+
return records;
|
|
10413
|
+
}
|
|
9877
10414
|
};
|
|
9878
10415
|
|
|
9879
10416
|
// src/runtime/services/record/record-resolver.service.ts
|
|
@@ -9968,6 +10505,280 @@ var RecordResolverService = class extends BaseService {
|
|
|
9968
10505
|
}
|
|
9969
10506
|
};
|
|
9970
10507
|
|
|
10508
|
+
// src/runtime/services/record/relation-properties.service.ts
|
|
10509
|
+
import { z as z5 } from "zod";
|
|
10510
|
+
var RelationPropertiesService = class extends BaseService {
|
|
10511
|
+
constructor(adapter) {
|
|
10512
|
+
super(adapter);
|
|
10513
|
+
}
|
|
10514
|
+
// ============================================================================
|
|
10515
|
+
// PUBLIC API
|
|
10516
|
+
// ============================================================================
|
|
10517
|
+
/**
|
|
10518
|
+
* Normalize relation values for storage in object_records table.
|
|
10519
|
+
*
|
|
10520
|
+
* Extracts IDs from hybrid format ({ id, props }) and returns legacy format (string[] or string).
|
|
10521
|
+
* This ensures object_records.values only contains IDs, while properties are in relation_attributes.
|
|
10522
|
+
*
|
|
10523
|
+
* @param schema - Object schema
|
|
10524
|
+
* @param data - Record data with hybrid relation values
|
|
10525
|
+
* @returns Data with relation values normalized to ID-only format
|
|
10526
|
+
*/
|
|
10527
|
+
normalizeRelationValuesForStorage(schema, data) {
|
|
10528
|
+
const normalized = { ...data };
|
|
10529
|
+
for (const attr of schema.attributes) {
|
|
10530
|
+
if (attr.type !== "relation" || !attr.properties) {
|
|
10531
|
+
continue;
|
|
10532
|
+
}
|
|
10533
|
+
const value = data[attr.name];
|
|
10534
|
+
if (value === null || value === void 0) {
|
|
10535
|
+
continue;
|
|
10536
|
+
}
|
|
10537
|
+
if (attr.cardinality === "many" && Array.isArray(value)) {
|
|
10538
|
+
normalized[attr.name] = value.map((item) => {
|
|
10539
|
+
if (typeof item === "string") return item;
|
|
10540
|
+
if (typeof item === "object" && item !== null && "id" in item) {
|
|
10541
|
+
return item.id;
|
|
10542
|
+
}
|
|
10543
|
+
return item;
|
|
10544
|
+
});
|
|
10545
|
+
} else if (typeof value === "object" && value !== null && "id" in value) {
|
|
10546
|
+
normalized[attr.name] = value.id;
|
|
10547
|
+
}
|
|
10548
|
+
}
|
|
10549
|
+
return normalized;
|
|
10550
|
+
}
|
|
10551
|
+
/**
|
|
10552
|
+
* Synchronize relation properties for a given attribute.
|
|
10553
|
+
*
|
|
10554
|
+
* Handles:
|
|
10555
|
+
* - Format normalization (legacy → new)
|
|
10556
|
+
* - Validation of properties
|
|
10557
|
+
* - Upsert for present IDs
|
|
10558
|
+
* - Delete for absent IDs
|
|
10559
|
+
*
|
|
10560
|
+
* @param schema - Object schema
|
|
10561
|
+
* @param recordId - Source record ID
|
|
10562
|
+
* @param attributeName - Relation attribute name
|
|
10563
|
+
* @param relationValue - Relation value (hybrid format)
|
|
10564
|
+
* @param adapter - Database adapter
|
|
10565
|
+
*/
|
|
10566
|
+
async syncRelationProperties(schema, recordId, attributeName, relationValue, adapter) {
|
|
10567
|
+
const attribute = schema.attributes.find((a) => a.name === attributeName);
|
|
10568
|
+
if (!attribute || attribute.type !== "relation") {
|
|
10569
|
+
return;
|
|
10570
|
+
}
|
|
10571
|
+
if (!attribute.properties) {
|
|
10572
|
+
return;
|
|
10573
|
+
}
|
|
10574
|
+
const normalized = this.normalizeRelationValue(relationValue);
|
|
10575
|
+
for (const item of normalized) {
|
|
10576
|
+
if (item.props) {
|
|
10577
|
+
this.validateProperties(attribute.properties, item.props);
|
|
10578
|
+
}
|
|
10579
|
+
}
|
|
10580
|
+
const existing = await adapter.relationAttributes?.findBySource(
|
|
10581
|
+
schema.name,
|
|
10582
|
+
recordId,
|
|
10583
|
+
attributeName
|
|
10584
|
+
);
|
|
10585
|
+
const existingIds = new Set((existing ?? []).map((r) => r.toId));
|
|
10586
|
+
const newIds = new Set(normalized.map((item) => item.id));
|
|
10587
|
+
const toUpsert = normalized.filter((item) => item.props !== void 0);
|
|
10588
|
+
const toDelete = Array.from(existingIds).filter((id) => !newIds.has(id));
|
|
10589
|
+
if (toUpsert.length > 0 && adapter.relationAttributes) {
|
|
10590
|
+
const inputs = toUpsert.map((item) => ({
|
|
10591
|
+
fromObject: schema.name,
|
|
10592
|
+
fromId: recordId,
|
|
10593
|
+
fromAttribute: attributeName,
|
|
10594
|
+
toId: item.id,
|
|
10595
|
+
properties: item.props ?? {},
|
|
10596
|
+
updatedBy: this.userId ?? void 0,
|
|
10597
|
+
createdBy: this.userId ?? void 0
|
|
10598
|
+
}));
|
|
10599
|
+
await adapter.relationAttributes.upsertBatch(inputs);
|
|
10600
|
+
}
|
|
10601
|
+
if (toDelete.length > 0 && adapter.relationAttributes && existing) {
|
|
10602
|
+
for (const toId of toDelete) {
|
|
10603
|
+
const relation2 = existing.find((r) => r.toId === toId);
|
|
10604
|
+
if (relation2) {
|
|
10605
|
+
}
|
|
10606
|
+
}
|
|
10607
|
+
await adapter.relationAttributes.deleteBySource(schema.name, recordId, attributeName);
|
|
10608
|
+
if (toUpsert.length > 0) {
|
|
10609
|
+
const inputs = toUpsert.map((item) => ({
|
|
10610
|
+
fromObject: schema.name,
|
|
10611
|
+
fromId: recordId,
|
|
10612
|
+
fromAttribute: attributeName,
|
|
10613
|
+
toId: item.id,
|
|
10614
|
+
properties: item.props ?? {},
|
|
10615
|
+
updatedBy: this.userId ?? void 0,
|
|
10616
|
+
createdBy: this.userId ?? void 0
|
|
10617
|
+
}));
|
|
10618
|
+
await adapter.relationAttributes.upsertBatch(inputs);
|
|
10619
|
+
}
|
|
10620
|
+
}
|
|
10621
|
+
}
|
|
10622
|
+
/**
|
|
10623
|
+
* Validate relation properties against PropertySchema.
|
|
10624
|
+
*
|
|
10625
|
+
* Uses Zod for runtime validation based on PropertyDefinition types.
|
|
10626
|
+
*
|
|
10627
|
+
* @param propertySchema - Schema defining allowed properties
|
|
10628
|
+
* @param properties - Properties to validate
|
|
10629
|
+
* @throws {z.ZodError} if validation fails
|
|
10630
|
+
*/
|
|
10631
|
+
validateProperties(propertySchema, properties) {
|
|
10632
|
+
const schema = this.buildZodSchema(propertySchema);
|
|
10633
|
+
schema.parse(properties);
|
|
10634
|
+
}
|
|
10635
|
+
// ============================================================================
|
|
10636
|
+
// PRIVATE HELPERS
|
|
10637
|
+
// ============================================================================
|
|
10638
|
+
/**
|
|
10639
|
+
* Normalize relation value to unified internal format.
|
|
10640
|
+
*
|
|
10641
|
+
* Converts:
|
|
10642
|
+
* - string[] → Array<{ id, props?: undefined }>
|
|
10643
|
+
* - string → [{ id, props?: undefined }]
|
|
10644
|
+
* - null → []
|
|
10645
|
+
* - Array<{ id, props }> → Array<{ id, props }> (passthrough)
|
|
10646
|
+
* - { id, props } → [{ id, props }] (single to array)
|
|
10647
|
+
*
|
|
10648
|
+
* @param value - Relation value in hybrid format
|
|
10649
|
+
* @returns Normalized array of relation items
|
|
10650
|
+
* @private
|
|
10651
|
+
*/
|
|
10652
|
+
normalizeRelationValue(value) {
|
|
10653
|
+
if (value === null || value === void 0) {
|
|
10654
|
+
return [];
|
|
10655
|
+
}
|
|
10656
|
+
if (typeof value === "string") {
|
|
10657
|
+
return [{ id: value }];
|
|
10658
|
+
}
|
|
10659
|
+
if (!Array.isArray(value) && typeof value === "object" && "id" in value) {
|
|
10660
|
+
return [value];
|
|
10661
|
+
}
|
|
10662
|
+
if (Array.isArray(value)) {
|
|
10663
|
+
return value.map((item) => {
|
|
10664
|
+
if (typeof item === "string") {
|
|
10665
|
+
return { id: item };
|
|
10666
|
+
}
|
|
10667
|
+
return item;
|
|
10668
|
+
});
|
|
10669
|
+
}
|
|
10670
|
+
return [];
|
|
10671
|
+
}
|
|
10672
|
+
/**
|
|
10673
|
+
* Build Zod schema from PropertySchema definition.
|
|
10674
|
+
*
|
|
10675
|
+
* Dynamically generates validation schema based on PropertyDefinition types.
|
|
10676
|
+
*
|
|
10677
|
+
* @param propertySchema - PropertySchema with definitions
|
|
10678
|
+
* @returns Zod schema for validation
|
|
10679
|
+
* @private
|
|
10680
|
+
*/
|
|
10681
|
+
buildZodSchema(propertySchema) {
|
|
10682
|
+
const shape = {};
|
|
10683
|
+
for (const def of propertySchema.definitions) {
|
|
10684
|
+
let fieldSchema = this.buildFieldSchema(def);
|
|
10685
|
+
if (!def.required) {
|
|
10686
|
+
fieldSchema = fieldSchema.optional();
|
|
10687
|
+
}
|
|
10688
|
+
shape[def.name] = fieldSchema;
|
|
10689
|
+
}
|
|
10690
|
+
return z5.object(shape);
|
|
10691
|
+
}
|
|
10692
|
+
/**
|
|
10693
|
+
* Build Zod schema for a single property field.
|
|
10694
|
+
*
|
|
10695
|
+
* @param def - PropertyDefinition
|
|
10696
|
+
* @returns Zod schema for the field
|
|
10697
|
+
* @private
|
|
10698
|
+
*/
|
|
10699
|
+
buildFieldSchema(def) {
|
|
10700
|
+
switch (def.type) {
|
|
10701
|
+
case "text":
|
|
10702
|
+
case "textarea": {
|
|
10703
|
+
let schema = z5.string();
|
|
10704
|
+
if (def.minLength !== void 0) {
|
|
10705
|
+
schema = schema.min(def.minLength);
|
|
10706
|
+
}
|
|
10707
|
+
if (def.maxLength !== void 0) {
|
|
10708
|
+
schema = schema.max(def.maxLength);
|
|
10709
|
+
}
|
|
10710
|
+
if (def.type === "text" && def.pattern) {
|
|
10711
|
+
schema = schema.regex(new RegExp(def.pattern));
|
|
10712
|
+
}
|
|
10713
|
+
return schema;
|
|
10714
|
+
}
|
|
10715
|
+
case "number": {
|
|
10716
|
+
let schema = z5.number();
|
|
10717
|
+
if (def.min !== void 0) {
|
|
10718
|
+
schema = schema.min(def.min);
|
|
10719
|
+
}
|
|
10720
|
+
if (def.max !== void 0) {
|
|
10721
|
+
schema = schema.max(def.max);
|
|
10722
|
+
}
|
|
10723
|
+
if (def.integer) {
|
|
10724
|
+
schema = schema.int();
|
|
10725
|
+
}
|
|
10726
|
+
return schema;
|
|
10727
|
+
}
|
|
10728
|
+
case "checkbox": {
|
|
10729
|
+
return z5.boolean();
|
|
10730
|
+
}
|
|
10731
|
+
case "date": {
|
|
10732
|
+
const schema = z5.string().datetime();
|
|
10733
|
+
return schema;
|
|
10734
|
+
}
|
|
10735
|
+
case "phone": {
|
|
10736
|
+
return z5.string();
|
|
10737
|
+
}
|
|
10738
|
+
case "currency": {
|
|
10739
|
+
let schema = z5.number();
|
|
10740
|
+
if (def.min !== void 0) {
|
|
10741
|
+
schema = schema.min(def.min);
|
|
10742
|
+
}
|
|
10743
|
+
if (def.max !== void 0) {
|
|
10744
|
+
schema = schema.max(def.max);
|
|
10745
|
+
}
|
|
10746
|
+
return schema;
|
|
10747
|
+
}
|
|
10748
|
+
case "status":
|
|
10749
|
+
case "select": {
|
|
10750
|
+
const validValues = def.options.map((opt) => opt.value);
|
|
10751
|
+
return z5.enum(validValues);
|
|
10752
|
+
}
|
|
10753
|
+
case "multiselect": {
|
|
10754
|
+
const validValues = def.options.map((opt) => opt.value);
|
|
10755
|
+
let schema = z5.array(z5.enum(validValues));
|
|
10756
|
+
if (def.maxSelections !== void 0) {
|
|
10757
|
+
schema = schema.max(def.maxSelections);
|
|
10758
|
+
}
|
|
10759
|
+
return schema;
|
|
10760
|
+
}
|
|
10761
|
+
case "rating": {
|
|
10762
|
+
let schema = z5.number().int();
|
|
10763
|
+
if (def.max !== void 0) {
|
|
10764
|
+
schema = schema.max(def.max);
|
|
10765
|
+
}
|
|
10766
|
+
return schema.min(0);
|
|
10767
|
+
}
|
|
10768
|
+
case "location": {
|
|
10769
|
+
return z5.object({
|
|
10770
|
+
address: z5.string().optional(),
|
|
10771
|
+
lat: z5.number().optional(),
|
|
10772
|
+
lng: z5.number().optional()
|
|
10773
|
+
});
|
|
10774
|
+
}
|
|
10775
|
+
default: {
|
|
10776
|
+
return z5.unknown();
|
|
10777
|
+
}
|
|
10778
|
+
}
|
|
10779
|
+
}
|
|
10780
|
+
};
|
|
10781
|
+
|
|
9971
10782
|
// src/runtime/services/record/relation.service.ts
|
|
9972
10783
|
var RelationService = class extends BaseService {
|
|
9973
10784
|
constructor(adapter, nativeRegistry, options) {
|
|
@@ -10796,6 +11607,7 @@ var RecordService = class extends BaseService {
|
|
|
10796
11607
|
queryService: this.queryService,
|
|
10797
11608
|
recordResolver: this.recordResolver
|
|
10798
11609
|
});
|
|
11610
|
+
this.relationPropertiesService = new RelationPropertiesService(adapter);
|
|
10799
11611
|
this.rollupService = new RollupService(adapter, {
|
|
10800
11612
|
recordResolver: this.recordResolver
|
|
10801
11613
|
});
|
|
@@ -10840,29 +11652,45 @@ var RecordService = class extends BaseService {
|
|
|
10840
11652
|
if (!options?.skipHooks) {
|
|
10841
11653
|
await this.hookRegistry.execute("beforeCreate", schema.name, hookCtx);
|
|
10842
11654
|
}
|
|
11655
|
+
const normalizedData = this.relationPropertiesService.normalizeRelationValuesForStorage(
|
|
11656
|
+
schema,
|
|
11657
|
+
dataWithDefaults
|
|
11658
|
+
);
|
|
10843
11659
|
if (options?.validate !== false) {
|
|
10844
11660
|
if (options?.allowDraft) {
|
|
10845
|
-
validateDraftOrThrow(schema,
|
|
11661
|
+
validateDraftOrThrow(schema, normalizedData);
|
|
10846
11662
|
} else {
|
|
10847
|
-
validateObjectOrThrow(schema,
|
|
11663
|
+
validateObjectOrThrow(schema, normalizedData);
|
|
10848
11664
|
}
|
|
10849
11665
|
if (!options?.skipRelationValidation) {
|
|
10850
|
-
await this.relationService.validateRelationsOrThrow(schema,
|
|
11666
|
+
await this.relationService.validateRelationsOrThrow(schema, normalizedData);
|
|
10851
11667
|
}
|
|
10852
11668
|
if (!options?.skipUserValidation) {
|
|
10853
|
-
await this.userService.validateUsersOrThrow(schema,
|
|
11669
|
+
await this.userService.validateUsersOrThrow(schema, normalizedData);
|
|
10854
11670
|
}
|
|
10855
11671
|
}
|
|
10856
|
-
const completionStatus = computeRecordStatus(schema,
|
|
10857
|
-
const label = await computeLabel(schema,
|
|
11672
|
+
const completionStatus = computeRecordStatus(schema, normalizedData);
|
|
11673
|
+
const label = await computeLabel(schema, normalizedData, this.labelResolver);
|
|
10858
11674
|
const record = await this.adapter.objectRecords.create({
|
|
10859
11675
|
objectId,
|
|
10860
|
-
data:
|
|
11676
|
+
data: normalizedData,
|
|
10861
11677
|
label,
|
|
10862
11678
|
completionStatus,
|
|
10863
11679
|
metadata: options?.metadata,
|
|
10864
11680
|
createdBy: this.userId
|
|
10865
11681
|
});
|
|
11682
|
+
for (const [attrName, value] of Object.entries(dataWithDefaults)) {
|
|
11683
|
+
const attr = schema.attributes.find((a) => a.name === attrName);
|
|
11684
|
+
if (attr?.type === "relation" && attr.properties) {
|
|
11685
|
+
await this.relationPropertiesService.syncRelationProperties(
|
|
11686
|
+
schema,
|
|
11687
|
+
record.id,
|
|
11688
|
+
attrName,
|
|
11689
|
+
value,
|
|
11690
|
+
this.adapter
|
|
11691
|
+
);
|
|
11692
|
+
}
|
|
11693
|
+
}
|
|
10866
11694
|
if (!options?.skipHooks) {
|
|
10867
11695
|
const afterCtx = {
|
|
10868
11696
|
...hookCtx,
|
|
@@ -10993,30 +11821,29 @@ var RecordService = class extends BaseService {
|
|
|
10993
11821
|
hookModifiedValues[key] = hookCtx.newValues[key];
|
|
10994
11822
|
}
|
|
10995
11823
|
}
|
|
11824
|
+
const dataToUpdate = { ...data, ...hookModifiedValues };
|
|
11825
|
+
const normalizedUpdate = this.relationPropertiesService.normalizeRelationValuesForStorage(
|
|
11826
|
+
schema,
|
|
11827
|
+
dataToUpdate
|
|
11828
|
+
);
|
|
11829
|
+
const normalizedMergedData = { ...existing.values, ...normalizedUpdate };
|
|
10996
11830
|
if (options?.validate !== false) {
|
|
10997
11831
|
if (options?.partial) {
|
|
10998
|
-
validateDraftOrThrow(schema,
|
|
11832
|
+
validateDraftOrThrow(schema, normalizedMergedData);
|
|
10999
11833
|
} else {
|
|
11000
|
-
validateObjectOrThrow(schema,
|
|
11834
|
+
validateObjectOrThrow(schema, normalizedMergedData);
|
|
11001
11835
|
}
|
|
11002
11836
|
if (!options?.skipRelationValidation) {
|
|
11003
|
-
await this.relationService.validateRelationsOrThrow(schema,
|
|
11004
|
-
...data,
|
|
11005
|
-
...hookModifiedValues
|
|
11006
|
-
});
|
|
11837
|
+
await this.relationService.validateRelationsOrThrow(schema, normalizedUpdate);
|
|
11007
11838
|
}
|
|
11008
11839
|
if (!options?.skipUserValidation) {
|
|
11009
|
-
await this.userService.validateUsersOrThrow(schema,
|
|
11010
|
-
...data,
|
|
11011
|
-
...hookModifiedValues
|
|
11012
|
-
});
|
|
11840
|
+
await this.userService.validateUsersOrThrow(schema, normalizedUpdate);
|
|
11013
11841
|
}
|
|
11014
11842
|
}
|
|
11015
|
-
const completionStatus = computeRecordStatus(schema,
|
|
11016
|
-
const label = await computeLabel(schema,
|
|
11843
|
+
const completionStatus = computeRecordStatus(schema, normalizedMergedData);
|
|
11844
|
+
const label = await computeLabel(schema, normalizedMergedData, this.labelResolver);
|
|
11017
11845
|
const updatePayload = {
|
|
11018
|
-
...
|
|
11019
|
-
...hookModifiedValues,
|
|
11846
|
+
...normalizedUpdate,
|
|
11020
11847
|
__completionStatus: completionStatus,
|
|
11021
11848
|
__label: label,
|
|
11022
11849
|
__lastUpdatedBy: this.userId,
|
|
@@ -11032,6 +11859,18 @@ var RecordService = class extends BaseService {
|
|
|
11032
11859
|
}
|
|
11033
11860
|
const updated = await this.adapter.objectRecords.update(recordId, updatePayload);
|
|
11034
11861
|
await this.invalidateRecordCaches(recordId, existing.objectId);
|
|
11862
|
+
for (const [attrName, value] of Object.entries(dataToUpdate)) {
|
|
11863
|
+
const attr = schema.attributes.find((a) => a.name === attrName);
|
|
11864
|
+
if (attr?.type === "relation" && attr.properties) {
|
|
11865
|
+
await this.relationPropertiesService.syncRelationProperties(
|
|
11866
|
+
schema,
|
|
11867
|
+
recordId,
|
|
11868
|
+
attrName,
|
|
11869
|
+
value,
|
|
11870
|
+
this.adapter
|
|
11871
|
+
);
|
|
11872
|
+
}
|
|
11873
|
+
}
|
|
11035
11874
|
if (!options?.skipHooks) {
|
|
11036
11875
|
const afterCtx = {
|
|
11037
11876
|
...hookCtx,
|
|
@@ -15707,23 +16546,6 @@ var GlobalSearchService = class extends BaseService {
|
|
|
15707
16546
|
* @param query - Search query string
|
|
15708
16547
|
* @param options - Search options (pagination, object filters)
|
|
15709
16548
|
* @returns Matching records with object metadata and total count
|
|
15710
|
-
*
|
|
15711
|
-
* @example
|
|
15712
|
-
* ```typescript
|
|
15713
|
-
* // Basic search
|
|
15714
|
-
* const { results, total } = await service.search("nike air");
|
|
15715
|
-
*
|
|
15716
|
-
* // With pagination
|
|
15717
|
-
* const { results, total } = await service.search("nike", {
|
|
15718
|
-
* limit: 10,
|
|
15719
|
-
* offset: 20
|
|
15720
|
-
* });
|
|
15721
|
-
*
|
|
15722
|
-
* // Filter by object types
|
|
15723
|
-
* const { results, total } = await service.search("nike", {
|
|
15724
|
-
* objectNames: ["products", "orders"]
|
|
15725
|
-
* });
|
|
15726
|
-
* ```
|
|
15727
16549
|
*/
|
|
15728
16550
|
async search(query, options) {
|
|
15729
16551
|
if (!query || query.trim().length === 0) {
|
|
@@ -15731,58 +16553,36 @@ var GlobalSearchService = class extends BaseService {
|
|
|
15731
16553
|
}
|
|
15732
16554
|
return this.cachedList(
|
|
15733
16555
|
"globalSearch",
|
|
15734
|
-
"
|
|
16556
|
+
"search",
|
|
15735
16557
|
{ query: query.trim(), ...options },
|
|
15736
|
-
() => this.
|
|
16558
|
+
() => this.adapter.objectRecords.globalSearch(query.trim(), {
|
|
16559
|
+
limit: options?.limit ?? 20,
|
|
16560
|
+
offset: options?.offset ?? 0,
|
|
16561
|
+
objectNames: options?.objectNames
|
|
16562
|
+
})
|
|
15737
16563
|
);
|
|
15738
16564
|
}
|
|
15739
16565
|
/**
|
|
15740
|
-
*
|
|
15741
|
-
|
|
15742
|
-
async executeSearch(query, options) {
|
|
15743
|
-
return await this.adapter.objectRecords.globalSearch(query, {
|
|
15744
|
-
limit: options?.limit ?? 20,
|
|
15745
|
-
offset: options?.offset ?? 0,
|
|
15746
|
-
objectNames: options?.objectNames,
|
|
15747
|
-
includeObjectInfo: options?.includeObjectInfo ?? true
|
|
15748
|
-
});
|
|
15749
|
-
}
|
|
15750
|
-
/**
|
|
15751
|
-
* Search and group results by object type
|
|
16566
|
+
* Search and group results by object type.
|
|
16567
|
+
* Delegates grouping to the database for accurate per-group counts.
|
|
15752
16568
|
*
|
|
15753
16569
|
* @param query - Search query string
|
|
15754
|
-
* @param options - Search options
|
|
15755
|
-
* @returns Results grouped by object name
|
|
16570
|
+
* @param options - Search options (object filters, limit per group)
|
|
16571
|
+
* @returns Results grouped by object name with per-group totals
|
|
15756
16572
|
*/
|
|
15757
16573
|
async searchGrouped(query, options) {
|
|
15758
|
-
|
|
15759
|
-
|
|
15760
|
-
const fetchLimit = Math.min(limitPerGroup * estimatedGroupCount, 100);
|
|
15761
|
-
const { results, total } = await this.search(query, {
|
|
15762
|
-
...options,
|
|
15763
|
-
limit: fetchLimit,
|
|
15764
|
-
offset: 0
|
|
15765
|
-
});
|
|
15766
|
-
const groupMap = /* @__PURE__ */ new Map();
|
|
15767
|
-
for (const result of results) {
|
|
15768
|
-
const existing = groupMap.get(result.objectName);
|
|
15769
|
-
if (existing) {
|
|
15770
|
-
existing.results.push(result);
|
|
15771
|
-
} else {
|
|
15772
|
-
groupMap.set(result.objectName, {
|
|
15773
|
-
objectName: result.objectName,
|
|
15774
|
-
objectLabel: result.objectLabel,
|
|
15775
|
-
results: [result]
|
|
15776
|
-
});
|
|
15777
|
-
}
|
|
16574
|
+
if (!query || query.trim().length === 0) {
|
|
16575
|
+
return { groups: [], total: 0 };
|
|
15778
16576
|
}
|
|
15779
|
-
|
|
15780
|
-
|
|
15781
|
-
|
|
15782
|
-
|
|
15783
|
-
|
|
15784
|
-
|
|
15785
|
-
|
|
16577
|
+
return this.cachedList(
|
|
16578
|
+
"globalSearch",
|
|
16579
|
+
"grouped",
|
|
16580
|
+
{ query: query.trim(), ...options },
|
|
16581
|
+
() => this.adapter.objectRecords.globalSearchGrouped(query.trim(), {
|
|
16582
|
+
objectNames: options?.objectNames,
|
|
16583
|
+
limitPerGroup: options?.limitPerGroup ?? 5
|
|
16584
|
+
})
|
|
16585
|
+
);
|
|
15786
16586
|
}
|
|
15787
16587
|
};
|
|
15788
16588
|
|
|
@@ -17132,6 +17932,7 @@ export {
|
|
|
17132
17932
|
SYSTEM_FIELD_NAMES,
|
|
17133
17933
|
RESERVED_ATTRIBUTE_NAMES,
|
|
17134
17934
|
PolicyViolationError,
|
|
17935
|
+
FORBIDDEN_PROPERTY_TYPES,
|
|
17135
17936
|
SYSTEM_ATTRIBUTES,
|
|
17136
17937
|
getSystemAttributeList,
|
|
17137
17938
|
isSystemAttribute,
|
|
@@ -17230,6 +18031,22 @@ export {
|
|
|
17230
18031
|
RoleNotFoundError,
|
|
17231
18032
|
isForbiddenError,
|
|
17232
18033
|
ConcurrentModificationError,
|
|
18034
|
+
PropertySchemaBuilder,
|
|
18035
|
+
PropertyTypeBuilder,
|
|
18036
|
+
BasePropertyBuilder,
|
|
18037
|
+
TextPropertyBuilder,
|
|
18038
|
+
TextareaPropertyBuilder,
|
|
18039
|
+
NumberPropertyBuilder,
|
|
18040
|
+
CheckboxPropertyBuilder,
|
|
18041
|
+
DatePropertyBuilder,
|
|
18042
|
+
PhonePropertyBuilder,
|
|
18043
|
+
CurrencyPropertyBuilder,
|
|
18044
|
+
StatusPropertyBuilder,
|
|
18045
|
+
SelectPropertyBuilder,
|
|
18046
|
+
MultiselectPropertyBuilder,
|
|
18047
|
+
RatingPropertyBuilder,
|
|
18048
|
+
LocationPropertyBuilder,
|
|
18049
|
+
validatePropertyType,
|
|
17233
18050
|
text,
|
|
17234
18051
|
textarea,
|
|
17235
18052
|
richtext,
|
|
@@ -17399,6 +18216,7 @@ export {
|
|
|
17399
18216
|
recalculateParentRollups,
|
|
17400
18217
|
RecordQueryService,
|
|
17401
18218
|
RecordResolverService,
|
|
18219
|
+
RelationPropertiesService,
|
|
17402
18220
|
RelationService,
|
|
17403
18221
|
RollupService,
|
|
17404
18222
|
RecordService,
|