@uipath/packager-tool-datafabric 1.201.0-preview.134

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.
@@ -0,0 +1,104 @@
1
+ import {
2
+ type EntityField,
3
+ type EntityJSON,
4
+ FieldDisplayType,
5
+ SqlTypeName,
6
+ } from "@uipath/entity-modeler/schema";
7
+
8
+ /** Data Fabric resolves this on actual resource creation. Same sentinel Studio
9
+ * Web writes (`UNASSIGNED_FOLDER_ID` in custom-render/data-service). */
10
+ const UNASSIGNED_FOLDER_ID = "99999999-9999-9999-9999-999999999999";
11
+
12
+ /** Every property a system field pins, mirroring Studio Web's
13
+ * `SYSTEM_FIELD_DEFAULTS`. Each template below overrides only what differs. */
14
+ const SYSTEM_FIELD_DEFAULTS = {
15
+ IsPrimaryKey: false,
16
+ IsForeignKey: false,
17
+ IsExternalField: false,
18
+ IsHiddenField: true,
19
+ FieldCategoryId: 0,
20
+ IsUnique: false,
21
+ ReferenceType: "ManyToOne",
22
+ Transformation: null,
23
+ IsRequired: false,
24
+ IsEncrypted: false,
25
+ Description: "",
26
+ IsSystemField: true,
27
+ FieldDisplayType: FieldDisplayType.Basic,
28
+ IsAttachment: false,
29
+ IsRbacEnabled: false,
30
+ IsModelReserved: false,
31
+ } as const satisfies Partial<EntityField>;
32
+
33
+ /** The 5 canonical system fields, in Studio Web's order. Also the validator's
34
+ * source of truth for "unmodified system fields": every property here is
35
+ * compared, and properties absent here are not. */
36
+ export const SYSTEM_FIELD_TEMPLATES: readonly EntityField[] = [
37
+ {
38
+ ...SYSTEM_FIELD_DEFAULTS,
39
+ Name: "Id",
40
+ DisplayName: "Id",
41
+ IsPrimaryKey: true,
42
+ SqlType: { Name: SqlTypeName.UNIQUEIDENTIFIER },
43
+ },
44
+ {
45
+ ...SYSTEM_FIELD_DEFAULTS,
46
+ Name: "CreateTime",
47
+ DisplayName: "CreateTime",
48
+ SqlType: { Name: SqlTypeName.DATETIMEOFFSET },
49
+ },
50
+ {
51
+ ...SYSTEM_FIELD_DEFAULTS,
52
+ Name: "CreatedBy",
53
+ DisplayName: "CreatedBy",
54
+ FieldDisplayType: FieldDisplayType.Relationship,
55
+ SqlType: { Name: SqlTypeName.UNIQUEIDENTIFIER },
56
+ },
57
+ {
58
+ ...SYSTEM_FIELD_DEFAULTS,
59
+ Name: "UpdateTime",
60
+ DisplayName: "UpdateTime",
61
+ SqlType: { Name: SqlTypeName.DATETIMEOFFSET },
62
+ },
63
+ {
64
+ ...SYSTEM_FIELD_DEFAULTS,
65
+ Name: "UpdatedBy",
66
+ DisplayName: "UpdatedBy",
67
+ FieldDisplayType: FieldDisplayType.Relationship,
68
+ SqlType: { Name: SqlTypeName.UNIQUEIDENTIFIER },
69
+ },
70
+ ];
71
+
72
+ /**
73
+ * A new entity's starting schema: the 5 system fields and the empty runtime
74
+ * defaults, in the order and shape Studio Web's `_initializeFromValues` writes,
75
+ * so an entity authored here and one authored there are the same document.
76
+ * Pure, no I/O. A system-fields-only entity is valid and deployable. `name` is
77
+ * used as-is — validity is the validator's concern, not this factory's.
78
+ */
79
+ export function createEntitySkeleton(name: string): EntityJSON {
80
+ return {
81
+ Id: globalThis.crypto.randomUUID(),
82
+ Name: name,
83
+ DisplayName: name,
84
+ EntityTypeId: 0,
85
+ EntityType: "Entity",
86
+ Description: "",
87
+ FolderId: UNASSIGNED_FOLDER_ID,
88
+ Fields: SYSTEM_FIELD_TEMPLATES.map((template) => ({
89
+ ...template,
90
+ SqlType: { ...template.SqlType },
91
+ })),
92
+ Data: null,
93
+ ExternalFields: null,
94
+ SourceJoinCriterias: null,
95
+ RecordCount: 0,
96
+ StorageSizeInMB: null,
97
+ UsedStorageSizeInMB: null,
98
+ AttachmentSizeInByte: null,
99
+ IsRbacEnabled: false,
100
+ InvalidIdentifiers: [],
101
+ IsModelReserved: false,
102
+ CategoryId: null,
103
+ };
104
+ }
@@ -0,0 +1,208 @@
1
+ import { findReservedNameError } from "@uipath/common/entity-name-rules";
2
+ import {
3
+ ALLOWED_TYPE_PAIRS,
4
+ type EntityField,
5
+ type EntityJSON,
6
+ NAME_PATTERN,
7
+ } from "@uipath/entity-modeler/schema";
8
+ import { SYSTEM_FIELD_TEMPLATES } from "./create-entity-skeleton.js";
9
+
10
+ /** Stable diagnostic codes: the editor keys Problems entries and pack output on
11
+ * these. Messages are for humans and may change. */
12
+ export const DIAGNOSTIC_CODES = {
13
+ entityNameInvalid: "ENTITY_NAME_INVALID",
14
+ entityNameNotUnique: "ENTITY_NAME_NOT_UNIQUE",
15
+ fieldNameInvalid: "FIELD_NAME_INVALID",
16
+ duplicateFieldName: "DUPLICATE_FIELD_NAME",
17
+ fieldTypeNotAllowed: "FIELD_TYPE_NOT_ALLOWED",
18
+ nameReserved: "NAME_RESERVED",
19
+ systemFieldMissing: "SYSTEM_FIELD_MISSING",
20
+ systemFieldModified: "SYSTEM_FIELD_MODIFIED",
21
+ } as const;
22
+ export type DiagnosticCode =
23
+ (typeof DIAGNOSTIC_CODES)[keyof typeof DIAGNOSTIC_CODES];
24
+
25
+ export interface EntityDiagnostic {
26
+ code: DiagnosticCode;
27
+ /** Human-readable, actionable message. */
28
+ message: string;
29
+ severity: "error";
30
+ /** The field the diagnostic is about, when field-scoped. */
31
+ fieldName?: string;
32
+ }
33
+
34
+ export interface ValidateEntityContext {
35
+ /** Every OTHER entity name in the solution; only the host knows these. */
36
+ siblingNames: readonly string[];
37
+ }
38
+
39
+ /**
40
+ * Every schema rule in one place: the editor runs it on each edit and pack runs
41
+ * it again, so the two can never disagree. Returns ALL diagnostics rather than
42
+ * stopping at the first, and never throws.
43
+ */
44
+ export function validateEntity(
45
+ entity: EntityJSON,
46
+ ctx: ValidateEntityContext,
47
+ ): EntityDiagnostic[] {
48
+ const diagnostics: EntityDiagnostic[] = [];
49
+
50
+ if (!NAME_PATTERN.test(entity.Name)) {
51
+ diagnostics.push({
52
+ code: DIAGNOSTIC_CODES.entityNameInvalid,
53
+ message: `Entity name '${entity.Name}' must start with a letter and contain 3-100 letters or digits.`,
54
+ severity: "error",
55
+ });
56
+ }
57
+
58
+ // The server's reserved lists, shared with `uip df entities create` so the
59
+ // two authors cannot drift. Checked separately from the pattern above, which
60
+ // is the modeller's format rule.
61
+ const reservedEntityName = findReservedNameError(entity.Name, "entity");
62
+ if (reservedEntityName) {
63
+ diagnostics.push({
64
+ code: DIAGNOSTIC_CODES.nameReserved,
65
+ message: `${reservedEntityName.message}. ${reservedEntityName.instructions}`,
66
+ severity: "error",
67
+ });
68
+ }
69
+
70
+ const entityNameLower = entity.Name.toLowerCase();
71
+ if (
72
+ ctx.siblingNames.some(
73
+ (sibling) => sibling.toLowerCase() === entityNameLower,
74
+ )
75
+ ) {
76
+ diagnostics.push({
77
+ code: DIAGNOSTIC_CODES.entityNameNotUnique,
78
+ message: `An entity named '${entity.Name}' already exists in this solution. Entity names are solution-wide unique.`,
79
+ severity: "error",
80
+ });
81
+ }
82
+
83
+ const seenFieldNames = new Map<string, string>();
84
+ for (const field of entity.Fields) {
85
+ // Custom fields only: system names are canonical ("Id" is 2 chars) and
86
+ // pinned by the integrity check below.
87
+ if (!field.IsSystemField && !NAME_PATTERN.test(field.Name)) {
88
+ diagnostics.push({
89
+ code: DIAGNOSTIC_CODES.fieldNameInvalid,
90
+ message: `Field name '${field.Name}' must start with a letter and contain 3-100 letters or digits.`,
91
+ severity: "error",
92
+ fieldName: field.Name,
93
+ });
94
+ }
95
+
96
+ if (!field.IsSystemField) {
97
+ const reservedFieldName = findReservedNameError(
98
+ field.Name,
99
+ "field",
100
+ );
101
+ if (reservedFieldName) {
102
+ diagnostics.push({
103
+ code: DIAGNOSTIC_CODES.nameReserved,
104
+ message: `${reservedFieldName.message}. ${reservedFieldName.instructions}`,
105
+ severity: "error",
106
+ fieldName: field.Name,
107
+ });
108
+ }
109
+ }
110
+
111
+ const fieldNameLower = field.Name.toLowerCase();
112
+ const firstWithName = seenFieldNames.get(fieldNameLower);
113
+ if (firstWithName !== undefined) {
114
+ diagnostics.push({
115
+ code: DIAGNOSTIC_CODES.duplicateFieldName,
116
+ message: `Duplicate field name '${field.Name}' (names are case-insensitive; first defined as '${firstWithName}').`,
117
+ severity: "error",
118
+ fieldName: field.Name,
119
+ });
120
+ } else {
121
+ seenFieldNames.set(fieldNameLower, field.Name);
122
+ }
123
+
124
+ // Custom fields only, again: system fields use pairs the palette
125
+ // doesn't offer and are pinned by the integrity check instead. This is
126
+ // also what rejects custom Relationship fields.
127
+ if (
128
+ !field.IsSystemField &&
129
+ !ALLOWED_TYPE_PAIRS.has(
130
+ `${field.FieldDisplayType}:${field.SqlType.Name}`,
131
+ )
132
+ ) {
133
+ diagnostics.push({
134
+ code: DIAGNOSTIC_CODES.fieldTypeNotAllowed,
135
+ message:
136
+ `Field '${field.Name}' uses an unsupported type combination ` +
137
+ `(${field.FieldDisplayType} / ${field.SqlType.Name}).`,
138
+ severity: "error",
139
+ fieldName: field.Name,
140
+ });
141
+ }
142
+ }
143
+
144
+ diagnostics.push(...validateSystemFields(entity));
145
+
146
+ return diagnostics;
147
+ }
148
+
149
+ /** Properties Data Fabric assigns when it creates the resource. A document that
150
+ * has round-tripped through the service carries these; they are not edits. */
151
+ const SERVER_ASSIGNED_FIELD_PROPS: ReadonlySet<string> = new Set([
152
+ "Id",
153
+ "CreateTime",
154
+ "CreatedBy",
155
+ "UpdateTime",
156
+ "UpdatedBy",
157
+ ]);
158
+
159
+ /**
160
+ * Does `actual` still match the canonical template? Every property the template
161
+ * pins must be equal — Studio Web fixes all of them, since system fields are not
162
+ * editable — and `actual` may add nothing beyond the server-assigned set. The
163
+ * second half is what catches a retargeted system FK: `ReferenceEntity` is not a
164
+ * template property, so ignoring unknown keys would let it through.
165
+ */
166
+ function isUnmodifiedSystemField(
167
+ template: EntityField,
168
+ actual: EntityField,
169
+ ): boolean {
170
+ const equal = (left: unknown, right: unknown): boolean =>
171
+ JSON.stringify(left ?? null) === JSON.stringify(right ?? null);
172
+
173
+ for (const key of Object.keys(template) as Array<keyof EntityField>) {
174
+ if (!equal(template[key], actual[key])) {
175
+ return false;
176
+ }
177
+ }
178
+ return Object.keys(actual).every(
179
+ (key) => key in template || SERVER_ASSIGNED_FIELD_PROPS.has(key),
180
+ );
181
+ }
182
+
183
+ function validateSystemFields(entity: EntityJSON): EntityDiagnostic[] {
184
+ const diagnostics: EntityDiagnostic[] = [];
185
+ for (const template of SYSTEM_FIELD_TEMPLATES) {
186
+ const actual = entity.Fields.find(
187
+ (field) => field.Name === template.Name,
188
+ );
189
+ if (!actual) {
190
+ diagnostics.push({
191
+ code: DIAGNOSTIC_CODES.systemFieldMissing,
192
+ message: `System field '${template.Name}' is missing. System fields cannot be removed.`,
193
+ severity: "error",
194
+ fieldName: template.Name,
195
+ });
196
+ continue;
197
+ }
198
+ if (!isUnmodifiedSystemField(template, actual)) {
199
+ diagnostics.push({
200
+ code: DIAGNOSTIC_CODES.systemFieldModified,
201
+ message: `System field '${template.Name}' was modified. System fields are read-only.`,
202
+ severity: "error",
203
+ fieldName: template.Name,
204
+ });
205
+ }
206
+ }
207
+ return diagnostics;
208
+ }