sg-widgets-core 0.3.0 → 0.4.0
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/index.d.mts +90 -68
- package/dist/index.mjs +191 -5
- package/dist/mock.d.mts +39 -1
- package/dist/mock.mjs +267 -2
- package/dist/{client-CP9sWEVZ.d.mts → proxy-handler-C6XB6gss.d.mts} +311 -65
- package/dist/{client-izKszX7l.mjs → schema-write-D_WfMYW8.mjs} +384 -2
- package/package.json +1 -1
package/dist/mock.mjs
CHANGED
|
@@ -1,4 +1,269 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { E as pluralPath, F as toFilterArray, J as isLinkType, Q as operatorsFor, T as undeclaredField, V as NEGATING_OPERATORS, W as TIME_UNITS, Y as isNumericType, b as fieldSchemaOverride, c as toSchemaProperties, d as SgApiError, h as normalizeEventLogEntry, i as SchemaApiError, l as EVENT_LOG_FIELDS, o as fieldFromRaw, p as eventLogFilters, q as isFilterable, y as displayNameOf, z as predictFieldName } from "./schema-write-D_WfMYW8.mjs";
|
|
2
|
+
//#region src/schema-write-mock.ts
|
|
3
|
+
/** The names an update's refusal lists as the ones it takes (116_field_settings_create_update). */
|
|
4
|
+
const UPDATE_PROPERTIES = "[\"name\", \"visible\", \"description\", \"summary_default\", \"valid_values\", \"default_value\", \"custom_metadata\"]";
|
|
5
|
+
/** The data types whose default an update may change (116_field_settings_create_update). */
|
|
6
|
+
const DEFAULT_AFTER_CREATION = /* @__PURE__ */ new Set([
|
|
7
|
+
"status_list",
|
|
8
|
+
"list",
|
|
9
|
+
"checkbox"
|
|
10
|
+
]);
|
|
11
|
+
function refusal(status, error) {
|
|
12
|
+
const first = {
|
|
13
|
+
status,
|
|
14
|
+
code: error.code ?? (status === 404 ? 104 : 103),
|
|
15
|
+
title: error.title ?? null,
|
|
16
|
+
source: error.source ?? null,
|
|
17
|
+
detail: error.detail ?? null,
|
|
18
|
+
meta: null
|
|
19
|
+
};
|
|
20
|
+
return new SchemaApiError(status, { errors: [first] });
|
|
21
|
+
}
|
|
22
|
+
/** The 400 for a value sent empty, naming the property by its place in the list (116_field_settings_create_update). */
|
|
23
|
+
function unfilled(index) {
|
|
24
|
+
return refusal(400, {
|
|
25
|
+
title: "Request Parameters invalid.",
|
|
26
|
+
source: { properties: { [String(index)]: { value: ["value must be filled"] } } }
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function missing(entityType, field) {
|
|
30
|
+
return refusal(404, {
|
|
31
|
+
title: "Not Found",
|
|
32
|
+
detail: `Field '${entityType}.${field}' does not exist.`
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
function wrap(value, editable = false) {
|
|
36
|
+
return {
|
|
37
|
+
value,
|
|
38
|
+
editable
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
/** A normalised field back in the wrapped shape the API answers. */
|
|
42
|
+
function rawFromField(field) {
|
|
43
|
+
const properties = {};
|
|
44
|
+
if (field.defaultValue !== void 0) properties["default_value"] = wrap(field.defaultValue);
|
|
45
|
+
if (field.validValues) properties["valid_values"] = wrap(field.validValues);
|
|
46
|
+
if (field.validTypes) properties["valid_types"] = wrap(field.validTypes);
|
|
47
|
+
if (field.displayValues) properties["display_values"] = wrap(field.displayValues);
|
|
48
|
+
if (field.hiddenValues) properties["hidden_values"] = wrap(field.hiddenValues);
|
|
49
|
+
return {
|
|
50
|
+
name: wrap(field.displayName, true),
|
|
51
|
+
...field.description ? { description: wrap(field.description, true) } : {},
|
|
52
|
+
custom_metadata: wrap("", true),
|
|
53
|
+
entity_type: wrap(field.entityType),
|
|
54
|
+
data_type: wrap(String(field.dataType)),
|
|
55
|
+
editable: wrap(field.editable),
|
|
56
|
+
mandatory: wrap(field.mandatory),
|
|
57
|
+
unique: wrap(field.unique),
|
|
58
|
+
visible: wrap(true, field.hideable ?? false),
|
|
59
|
+
properties
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function apply(raw, changes) {
|
|
63
|
+
const next = structuredClone(raw);
|
|
64
|
+
for (const { property_name: key, value } of changes) if (key === "name") next.name = wrap(String(value), true);
|
|
65
|
+
else if (key === "description") next.description = wrap(String(value), next.description?.editable ?? true);
|
|
66
|
+
else if (key === "custom_metadata") next.custom_metadata = wrap(String(value), true);
|
|
67
|
+
else if (key === "unique") next.unique = wrap(Boolean(value));
|
|
68
|
+
else if (key === "visible") next.visible = wrap(Boolean(value), next.visible?.editable ?? true);
|
|
69
|
+
else if (key === "valid_types") next.properties[key] = wrap(value, next.data_type.value === "entity");
|
|
70
|
+
else next.properties[key] = wrap(value, next.properties[key]?.editable ?? false);
|
|
71
|
+
return next;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Throw what the server answers to an update it refuses (040_field_revive,
|
|
75
|
+
* 114_schema_admin_recheck, 116_field_settings_create_update). A default and
|
|
76
|
+
* its valid values changed in one update are not measured, and pass.
|
|
77
|
+
*/
|
|
78
|
+
function refuseUpdate(current, changes) {
|
|
79
|
+
const type = current.data_type.value;
|
|
80
|
+
const sent = new Set(changes.map((change) => change.property_name));
|
|
81
|
+
const held = (key) => current.properties[key]?.value;
|
|
82
|
+
for (const [index, { property_name: name, value }] of changes.entries()) {
|
|
83
|
+
if (name === "data_type") throw refusal(400, { title: "API schema_field_update() invalid property 'data_type'" });
|
|
84
|
+
if (name === "hidden_values") throw refusal(400, { title: `API schema_field_update() invalid property 'hidden_values': Valid property names: ["name", "visible", "description", "summary_default", "default_value", "valid_values", "custom_metadata"]` });
|
|
85
|
+
if (name === "unique") {
|
|
86
|
+
if (current.unique.value) throw refusal(500, {
|
|
87
|
+
code: 105,
|
|
88
|
+
title: "API schema_field_update() does not support updating 'unique'."
|
|
89
|
+
});
|
|
90
|
+
throw refusal(400, { title: `API schema_field_update() invalid property 'unique': Valid property names: ${UPDATE_PROPERTIES}` });
|
|
91
|
+
}
|
|
92
|
+
if (name === "valid_types" && type === "multi_entity") throw refusal(400, { title: "API schema_field_update() can't update 'valid_types' on this field" });
|
|
93
|
+
if (name === "default_value") {
|
|
94
|
+
if (value === null || value === "") throw unfilled(index);
|
|
95
|
+
if (!DEFAULT_AFTER_CREATION.has(type)) throw refusal(400, { title: "API schema_field_update() default value is read-only for all data types except status_list, list, checkbox" });
|
|
96
|
+
const values = held("valid_values");
|
|
97
|
+
if ((type === "list" || type === "status_list") && !sent.has("valid_values") && !(Array.isArray(values) && values.includes(value))) throw refusal(400, { title: `API schema_field_update() 'default_value' not in valid_values : ${JSON.stringify(values ?? [])}` });
|
|
98
|
+
}
|
|
99
|
+
if (name === "valid_values" && (type === "list" || type === "status_list")) {
|
|
100
|
+
if (Array.isArray(value) && value.length === 0) throw unfilled(index);
|
|
101
|
+
const fallback = held("default_value");
|
|
102
|
+
if (type === "status_list" && !sent.has("default_value") && typeof fallback === "string" && Array.isArray(value) && !value.includes(fallback)) throw refusal(400, { title: `API schema_field_update() 'valid_values' ${JSON.stringify(value)} must include the 'default_value' '${fallback}'` });
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
var MockSchemaClient = class {
|
|
107
|
+
base;
|
|
108
|
+
/** Fields this client created or wrote, by `Type.field`. */
|
|
109
|
+
live = /* @__PURE__ */ new Map();
|
|
110
|
+
/** Retired fields. The name stays taken and no read lists it (040_field_revive). */
|
|
111
|
+
retired = /* @__PURE__ */ new Map();
|
|
112
|
+
/** Writes made at a project's scope, by `Type.field@project`. */
|
|
113
|
+
scoped = /* @__PURE__ */ new Map();
|
|
114
|
+
pendingFailure;
|
|
115
|
+
constructor(options = {}) {
|
|
116
|
+
this.base = options.client ?? null;
|
|
117
|
+
this.pendingFailure = options.failNext ?? null;
|
|
118
|
+
}
|
|
119
|
+
/** Make the next call reject with a `SchemaApiError`. Pass `null` to disarm. */
|
|
120
|
+
failNext(failure = {}) {
|
|
121
|
+
this.pendingFailure = failure;
|
|
122
|
+
}
|
|
123
|
+
gate() {
|
|
124
|
+
const failure = this.pendingFailure;
|
|
125
|
+
this.pendingFailure = null;
|
|
126
|
+
if (failure) throw refusal(failure.status ?? 500, { title: failure.message ?? `Flow PT API error ${failure.status ?? 500}` });
|
|
127
|
+
}
|
|
128
|
+
/** The base client's fields of a type; an unknown type is its 404. */
|
|
129
|
+
async baseFields(entityType, projectId) {
|
|
130
|
+
if (!this.base) return {};
|
|
131
|
+
try {
|
|
132
|
+
return await this.base.fields(entityType, projectId);
|
|
133
|
+
} catch (error) {
|
|
134
|
+
if (error instanceof SgApiError && !(error instanceof SchemaApiError)) throw refusal(error.status, error.status === 404 ? {
|
|
135
|
+
title: "Not Found",
|
|
136
|
+
detail: error.message
|
|
137
|
+
} : { title: error.message });
|
|
138
|
+
throw error;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/** Every live record of a type at a scope, this client's writes over the base client's fields. */
|
|
142
|
+
async records(entityType, projectId) {
|
|
143
|
+
const out = {};
|
|
144
|
+
for (const [name, field] of Object.entries(await this.baseFields(entityType, projectId))) {
|
|
145
|
+
const key = `${entityType}.${name}`;
|
|
146
|
+
if (this.retired.has(key)) continue;
|
|
147
|
+
const written = this.live.get(key);
|
|
148
|
+
if (!written) {
|
|
149
|
+
out[name] = rawFromField(field);
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
const record = structuredClone(written);
|
|
153
|
+
if (field.hiddenValues) record.properties["hidden_values"] = wrap(field.hiddenValues);
|
|
154
|
+
else delete record.properties["hidden_values"];
|
|
155
|
+
out[name] = record;
|
|
156
|
+
}
|
|
157
|
+
for (const [key, record] of this.live) {
|
|
158
|
+
const name = key.slice(entityType.length + 1);
|
|
159
|
+
if (key.startsWith(`${entityType}.`) && !out[name]) out[name] = structuredClone(record);
|
|
160
|
+
}
|
|
161
|
+
if (projectId !== void 0) for (const [name, record] of Object.entries(out)) {
|
|
162
|
+
const changes = this.scoped.get(`${entityType}.${name}@${projectId}`);
|
|
163
|
+
if (changes) out[name] = apply(record, changes);
|
|
164
|
+
}
|
|
165
|
+
return out;
|
|
166
|
+
}
|
|
167
|
+
async readField(entityType, field, projectId) {
|
|
168
|
+
this.gate();
|
|
169
|
+
const raw = (await this.records(entityType, projectId))[field];
|
|
170
|
+
if (raw) return {
|
|
171
|
+
status: "found",
|
|
172
|
+
field: fieldFromRaw(entityType, field, raw),
|
|
173
|
+
raw
|
|
174
|
+
};
|
|
175
|
+
if (undeclaredField(entityType, field)) return { status: "undeclared" };
|
|
176
|
+
return {
|
|
177
|
+
status: "missing",
|
|
178
|
+
message: missing(entityType, field).message
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
async readRawFields(entityType, projectId) {
|
|
182
|
+
this.gate();
|
|
183
|
+
return this.records(entityType, projectId);
|
|
184
|
+
}
|
|
185
|
+
async createField(entityType, input) {
|
|
186
|
+
this.gate();
|
|
187
|
+
const existing = await this.records(entityType);
|
|
188
|
+
const given = toSchemaProperties(input.properties);
|
|
189
|
+
const valueOf = (key) => given.find((p) => p.property_name === key)?.value;
|
|
190
|
+
const type = input.dataType;
|
|
191
|
+
if (type === "color" || type === "image") throw refusal(400, {
|
|
192
|
+
title: "Request Parameters invalid.",
|
|
193
|
+
source: { data_type: ["data_type is not valid"] }
|
|
194
|
+
});
|
|
195
|
+
if (type === "calculated") throw refusal(500, { title: "NoMethodError" });
|
|
196
|
+
if (type === "checkbox" && typeof valueOf("default_value") !== "boolean") throw refusal(500, { title: "Only true or false allowed in checkbox" });
|
|
197
|
+
if (type === "status_list" && valueOf("valid_values") === void 0) throw refusal(400, { title: `API schema_field_create() missing required 'properties' ["valid_values"]` });
|
|
198
|
+
if (type === "entity" || type === "multi_entity") {
|
|
199
|
+
const validTypes = valueOf("valid_types");
|
|
200
|
+
if (validTypes === void 0) throw refusal(400, { title: "missing required 'properties'" });
|
|
201
|
+
const count = Array.isArray(validTypes) ? validTypes.length : 0;
|
|
202
|
+
if (type === "multi_entity" ? count !== 1 : count < 1) throw refusal(400, { title: "'valid_types' value expected Array with one element" });
|
|
203
|
+
}
|
|
204
|
+
const wanted = predictFieldName(input.displayName, type) ?? "sg_";
|
|
205
|
+
let name = wanted;
|
|
206
|
+
for (let n = 1; existing[name]; n += 1) name = `${wanted}_${n}`;
|
|
207
|
+
if (this.retired.has(`${entityType}.${name}`)) throw refusal(400, { title: `API schema_field_create() failed, there is a retired field with the same field_name: ${name}. Delete the retired field forever from the Trash Page in Shotgun and try again.` });
|
|
208
|
+
const record = apply({
|
|
209
|
+
name: wrap(input.displayName, true),
|
|
210
|
+
custom_metadata: wrap("", true),
|
|
211
|
+
entity_type: wrap(entityType),
|
|
212
|
+
data_type: wrap(type),
|
|
213
|
+
editable: wrap(true),
|
|
214
|
+
mandatory: wrap(false),
|
|
215
|
+
unique: wrap(false),
|
|
216
|
+
visible: wrap(true, true),
|
|
217
|
+
properties: { default_value: wrap(null) }
|
|
218
|
+
}, given);
|
|
219
|
+
this.live.set(`${entityType}.${name}`, record);
|
|
220
|
+
return {
|
|
221
|
+
name,
|
|
222
|
+
self: `/api/v1/schema/${entityType}/fields/${name}`,
|
|
223
|
+
raw: structuredClone(record)
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
async updateField(entityType, field, properties, projectId) {
|
|
227
|
+
this.gate();
|
|
228
|
+
const key = `${entityType}.${field}`;
|
|
229
|
+
const current = (await this.records(entityType))[field];
|
|
230
|
+
if (!current) throw missing(entityType, field);
|
|
231
|
+
const changes = toSchemaProperties(properties);
|
|
232
|
+
if (changes.length === 0) throw refusal(400, { title: "API schema_field_update() can't create/update with no 'properties'" });
|
|
233
|
+
refuseUpdate(current, changes);
|
|
234
|
+
if (projectId === void 0) this.live.set(key, apply(current, changes));
|
|
235
|
+
else {
|
|
236
|
+
const scope = `${key}@${projectId}`;
|
|
237
|
+
this.scoped.set(scope, [...this.scoped.get(scope) ?? [], ...changes]);
|
|
238
|
+
}
|
|
239
|
+
return (await this.records(entityType, projectId))[field] ?? null;
|
|
240
|
+
}
|
|
241
|
+
async retireField(entityType, field) {
|
|
242
|
+
this.gate();
|
|
243
|
+
const current = (await this.records(entityType))[field];
|
|
244
|
+
if (!current) throw missing(entityType, field);
|
|
245
|
+
const key = `${entityType}.${field}`;
|
|
246
|
+
this.live.delete(key);
|
|
247
|
+
this.retired.set(key, current);
|
|
248
|
+
}
|
|
249
|
+
async reviveField(entityType, field) {
|
|
250
|
+
this.gate();
|
|
251
|
+
const key = `${entityType}.${field}`;
|
|
252
|
+
const record = this.retired.get(key);
|
|
253
|
+
if (!record) {
|
|
254
|
+
const error = missing(entityType, field);
|
|
255
|
+
return {
|
|
256
|
+
revived: false,
|
|
257
|
+
status: error.status,
|
|
258
|
+
message: error.message
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
this.retired.delete(key);
|
|
262
|
+
this.live.set(key, record);
|
|
263
|
+
return { revived: true };
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
//#endregion
|
|
2
267
|
//#region src/mock.ts
|
|
3
268
|
/**
|
|
4
269
|
* Status vocabularies. `valid_values` is the site's whole vocabulary and is
|
|
@@ -3828,4 +4093,4 @@ function toStatusIcon(values) {
|
|
|
3828
4093
|
}
|
|
3829
4094
|
}
|
|
3830
4095
|
//#endregion
|
|
3831
|
-
export { MOCK_NOW, MockClient };
|
|
4096
|
+
export { MOCK_NOW, MockClient, MockSchemaClient };
|