@snowtop/ent 0.2.13 → 0.2.15
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/action/orchestrator.d.ts +6 -1
- package/action/orchestrator.js +164 -20
- package/action/privacy.d.ts +1 -0
- package/action/privacy.js +1 -0
- package/core/clause.js +1 -1
- package/core/query_impl.d.ts +2 -2
- package/core/query_impl.js +7 -5
- package/package.json +2 -2
- package/schema/base_schema.js +26 -39
- package/schema/struct_field.js +7 -5
- package/schema/union_field.js +3 -2
- package/scripts/custom_graphql.js +4 -1
- package/testutils/builder.d.ts +1 -0
- package/testutils/builder.js +11 -2
- package/testutils/db/temp_db.js +2 -1
package/action/orchestrator.d.ts
CHANGED
|
@@ -17,7 +17,7 @@ export interface OrchestratorOptions<TEnt extends Ent<TViewer>, TInput extends D
|
|
|
17
17
|
action?: Action<TEnt, Builder<TEnt, TViewer>, TViewer, TInput>;
|
|
18
18
|
schema: SchemaInputType;
|
|
19
19
|
editedFields(): Map<string, any> | Promise<Map<string, any>>;
|
|
20
|
-
updateInput?: (data: TInput) => void;
|
|
20
|
+
updateInput?: (data: TInput, operation?: WriteOperation, defaultKeys?: ReadonlySet<string>) => void;
|
|
21
21
|
expressions?: Map<string, clause.Clause>;
|
|
22
22
|
fieldInfo: FieldInfoMap;
|
|
23
23
|
}
|
|
@@ -38,6 +38,8 @@ export declare class Orchestrator<TEnt extends Ent<TViewer>, TInput extends Data
|
|
|
38
38
|
private options;
|
|
39
39
|
private edgeSet;
|
|
40
40
|
private edges;
|
|
41
|
+
private fieldEdgeSources;
|
|
42
|
+
private fieldEdgeInputs;
|
|
41
43
|
private conditionalEdges;
|
|
42
44
|
private validatedFields;
|
|
43
45
|
private logValues;
|
|
@@ -59,6 +61,9 @@ export declare class Orchestrator<TEnt extends Ent<TViewer>, TInput extends Data
|
|
|
59
61
|
setDisableTransformations(val: boolean): void;
|
|
60
62
|
setOnConflictOptions(onConflict: CreateRowOptions["onConflict"]): void;
|
|
61
63
|
addInboundEdge<T2 extends Ent>(id1: ID | Builder<T2, any>, edgeType: string, nodeType: string, options?: AssocEdgeInputOptions): void;
|
|
64
|
+
__setFieldEdges<T2 extends Ent>(fieldName: string, ids: readonly (ID | Builder<T2, any>)[] | undefined, edgeType: string, nodeType: string, stored: {
|
|
65
|
+
existingIDs?: readonly ID[];
|
|
66
|
+
}): void;
|
|
62
67
|
addOutboundEdge<T2 extends Ent>(id2: ID | Builder<T2, any>, edgeType: string, nodeType: string, options?: AssocEdgeInputOptions): void;
|
|
63
68
|
removeInboundEdge(id1: ID, edgeType: string, options?: AssocEdgeOptions): void;
|
|
64
69
|
removeOutboundEdge(id2: ID, edgeType: string, options?: AssocEdgeOptions): void;
|
package/action/orchestrator.js
CHANGED
|
@@ -39,12 +39,25 @@ const schema_1 = require("../schema/schema");
|
|
|
39
39
|
const operations_1 = require("./operations");
|
|
40
40
|
const action_1 = require("../action");
|
|
41
41
|
const privacy_1 = require("../core/privacy");
|
|
42
|
+
const privacy_2 = require("./privacy");
|
|
42
43
|
const executor_1 = require("./executor");
|
|
43
44
|
const memoize_1 = require("../core/memoize");
|
|
44
45
|
const logger_1 = require("../core/logger");
|
|
45
46
|
const clause = __importStar(require("../core/clause"));
|
|
46
47
|
const types_1 = require("util/types");
|
|
47
48
|
const operations_2 = require("./operations");
|
|
49
|
+
function invalidFieldError(fieldName, value) {
|
|
50
|
+
let description;
|
|
51
|
+
try {
|
|
52
|
+
description = `${value}`;
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
// Null-prototype records (and lists containing them) cannot be coerced
|
|
56
|
+
// to strings. Diagnostic formatting must not hide a validation failure.
|
|
57
|
+
description = "[unprintable value]";
|
|
58
|
+
}
|
|
59
|
+
return new Error(`invalid field ${fieldName} with value ${description}`);
|
|
60
|
+
}
|
|
48
61
|
var edgeDirection;
|
|
49
62
|
(function (edgeDirection) {
|
|
50
63
|
edgeDirection[edgeDirection["inboundEdge"] = 0] = "inboundEdge";
|
|
@@ -99,6 +112,8 @@ class Orchestrator {
|
|
|
99
112
|
this.options = options;
|
|
100
113
|
this.edgeSet = new Set();
|
|
101
114
|
this.edges = new Map();
|
|
115
|
+
this.fieldEdgeSources = new WeakMap();
|
|
116
|
+
this.fieldEdgeInputs = new Map();
|
|
102
117
|
this.conditionalEdges = new Map();
|
|
103
118
|
this.validatedFields = null;
|
|
104
119
|
this.changesets = [];
|
|
@@ -155,6 +170,105 @@ class Orchestrator {
|
|
|
155
170
|
direction: edgeDirection.inboundEdge,
|
|
156
171
|
}), action_1.WriteOperation.Insert, options?.conditional);
|
|
157
172
|
}
|
|
173
|
+
// Update inverse edges for generated fields while preserving explicit edge
|
|
174
|
+
// operations. This method is internal. For edits and deletions, use stored IDs.
|
|
175
|
+
// If `stored` omits `existingIDs`, reuse the IDs captured before synchronous
|
|
176
|
+
// default updates.
|
|
177
|
+
__setFieldEdges(fieldName, ids, edgeType, nodeType, stored) {
|
|
178
|
+
// Existing builders and literal IDs can refer to the same database row.
|
|
179
|
+
// Use placeholder IDs as queue keys so unsaved builders remain dependencies.
|
|
180
|
+
const endpointID = (id) => (0, privacy_2.isBuilder)(id) ? (id.existingEnt?.id ?? id.placeholderID) : id;
|
|
181
|
+
this.fieldEdgeInputs.set(fieldName, {
|
|
182
|
+
edgeType,
|
|
183
|
+
nodeType,
|
|
184
|
+
ids,
|
|
185
|
+
existingIDs: stored.existingIDs ??
|
|
186
|
+
this.fieldEdgeInputs.get(fieldName)?.existingIDs ??
|
|
187
|
+
[],
|
|
188
|
+
});
|
|
189
|
+
const inserts = new Map();
|
|
190
|
+
const removals = new Map();
|
|
191
|
+
const retained = new Set();
|
|
192
|
+
const contribute = (map, id, source) => {
|
|
193
|
+
const key = (0, privacy_2.isBuilder)(id) ? id.placeholderID : id;
|
|
194
|
+
let entry = map.get(key);
|
|
195
|
+
if (!entry) {
|
|
196
|
+
map.set(key, (entry = { id, sources: new Set() }));
|
|
197
|
+
}
|
|
198
|
+
entry.sources.add(source);
|
|
199
|
+
};
|
|
200
|
+
for (const [source, field] of this.fieldEdgeInputs) {
|
|
201
|
+
if (field.edgeType !== edgeType) {
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
const existing = this.actualOperation === action_1.WriteOperation.Insert ? [] : field.existingIDs;
|
|
205
|
+
const current = this.actualOperation === action_1.WriteOperation.Delete ? [] : field.ids;
|
|
206
|
+
for (const id of current ?? existing) {
|
|
207
|
+
retained.add(endpointID(id));
|
|
208
|
+
}
|
|
209
|
+
if (current !== undefined) {
|
|
210
|
+
for (const id of current) {
|
|
211
|
+
contribute(inserts, id, source);
|
|
212
|
+
}
|
|
213
|
+
for (const id of existing) {
|
|
214
|
+
contribute(removals, id, source);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const manualEndpoints = (op) => {
|
|
219
|
+
const endpoints = new Set();
|
|
220
|
+
for (const edge of this.edges.get(edgeType)?.get(op)?.values() ?? []) {
|
|
221
|
+
if (!this.fieldEdgeSources.has(edge)) {
|
|
222
|
+
endpoints.add(endpointID(edge.id));
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return endpoints;
|
|
226
|
+
};
|
|
227
|
+
const manualInserts = manualEndpoints(action_1.WriteOperation.Insert);
|
|
228
|
+
const manualRemovals = manualEndpoints(action_1.WriteOperation.Delete);
|
|
229
|
+
for (const id of removals.keys()) {
|
|
230
|
+
if (retained.has(id) || manualInserts.has(id)) {
|
|
231
|
+
removals.delete(id);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
for (const [key, contribution] of inserts) {
|
|
235
|
+
const id = endpointID(contribution.id);
|
|
236
|
+
if (manualInserts.has(id) || manualRemovals.has(id)) {
|
|
237
|
+
inserts.delete(key);
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
for (const [op, desired] of [
|
|
241
|
+
[action_1.WriteOperation.Insert, inserts],
|
|
242
|
+
[action_1.WriteOperation.Delete, removals],
|
|
243
|
+
]) {
|
|
244
|
+
const queued = this.edges.get(edgeType)?.get(op);
|
|
245
|
+
for (const [id, edge] of queued ?? []) {
|
|
246
|
+
if (!this.fieldEdgeSources.has(edge)) {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
const contribution = desired.get(id);
|
|
250
|
+
if (contribution) {
|
|
251
|
+
this.fieldEdgeSources.set(edge, contribution.sources);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
queued.delete(id);
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
for (const [key, contribution] of desired) {
|
|
258
|
+
if (this.edges.get(edgeType)?.get(op)?.has(key)) {
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
const edge = new edgeInputData({
|
|
262
|
+
id: contribution.id,
|
|
263
|
+
edgeType,
|
|
264
|
+
nodeType,
|
|
265
|
+
direction: edgeDirection.inboundEdge,
|
|
266
|
+
});
|
|
267
|
+
this.fieldEdgeSources.set(edge, contribution.sources);
|
|
268
|
+
this.addEdge(edge, op);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
158
272
|
addOutboundEdge(id2, edgeType, nodeType, options) {
|
|
159
273
|
this.addEdge(new edgeInputData({
|
|
160
274
|
id: id2,
|
|
@@ -532,27 +646,22 @@ class Orchestrator {
|
|
|
532
646
|
}
|
|
533
647
|
async triggers(action, builder, triggers) {
|
|
534
648
|
const groups = [];
|
|
535
|
-
let
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
// @ts-ignore
|
|
542
|
-
groups.push(triggers.slice(lastArray, i));
|
|
649
|
+
let group = [];
|
|
650
|
+
for (const trigger of triggers) {
|
|
651
|
+
if (Array.isArray(trigger)) {
|
|
652
|
+
if (group.length) {
|
|
653
|
+
groups.push(group);
|
|
654
|
+
group = [];
|
|
543
655
|
}
|
|
544
|
-
groups.push(
|
|
545
|
-
prevWasArray = true;
|
|
546
|
-
lastArray++;
|
|
656
|
+
groups.push(trigger);
|
|
547
657
|
}
|
|
548
658
|
else {
|
|
549
|
-
|
|
550
|
-
// @ts-ignore
|
|
551
|
-
groups.push(triggers.slice(lastArray, i + 1));
|
|
552
|
-
}
|
|
553
|
-
prevWasArray = false;
|
|
659
|
+
group.push(trigger);
|
|
554
660
|
}
|
|
555
661
|
}
|
|
662
|
+
if (group.length) {
|
|
663
|
+
groups.push(group);
|
|
664
|
+
}
|
|
556
665
|
for (const triggers of groups) {
|
|
557
666
|
await Promise.all(triggers.map(async (trigger) => {
|
|
558
667
|
let ret = await trigger.changeset(builder, action.getInput());
|
|
@@ -599,6 +708,8 @@ class Orchestrator {
|
|
|
599
708
|
async getFieldsWithDefaultValues(builder, schemaFields, editedFields, action) {
|
|
600
709
|
let data = {};
|
|
601
710
|
let defaultData = {};
|
|
711
|
+
const defaultKeys = new Set();
|
|
712
|
+
const transformedFields = new Set();
|
|
602
713
|
let input = action?.getInput() || {};
|
|
603
714
|
let updateInput = false;
|
|
604
715
|
// transformations
|
|
@@ -606,6 +717,8 @@ class Orchestrator {
|
|
|
606
717
|
// if disable transformations set, don't do schema transform and just do the right thing
|
|
607
718
|
// else apply schema tranformation if it exists
|
|
608
719
|
let transformed = null;
|
|
720
|
+
const initialOperation = this.actualOperation;
|
|
721
|
+
const initialEnt = this.existingEnt;
|
|
609
722
|
const sqlOp = this.getSQLStatementOperation();
|
|
610
723
|
// why is transform write technically different from upsert?
|
|
611
724
|
// it's create -> update just at the db level...
|
|
@@ -649,6 +762,7 @@ class Orchestrator {
|
|
|
649
762
|
}
|
|
650
763
|
data[this.getStorageKey(k)] = dbVal;
|
|
651
764
|
if (!field.immutable) {
|
|
765
|
+
transformedFields.add(k);
|
|
652
766
|
this.defaultFieldsByTSName[this.getInputKey(k)] = inputVal;
|
|
653
767
|
}
|
|
654
768
|
// hmm do we need this?
|
|
@@ -668,6 +782,13 @@ class Orchestrator {
|
|
|
668
782
|
builder.existingEnt = transformed.existingEnt;
|
|
669
783
|
}
|
|
670
784
|
}
|
|
785
|
+
if (this.fieldEdgeInputs.size > 0 &&
|
|
786
|
+
(initialOperation !== this.actualOperation ||
|
|
787
|
+
initialEnt !== this.existingEnt)) {
|
|
788
|
+
// Refresh inverse edges for the transformed operation and row before
|
|
789
|
+
// applying defaults or running triggers.
|
|
790
|
+
editedFields = await this.options.editedFields();
|
|
791
|
+
}
|
|
671
792
|
// transforming before doing default fields so that we don't create a new id
|
|
672
793
|
// and anything that depends on the type of operations knows what it is
|
|
673
794
|
const userDefinedKeys = new Set();
|
|
@@ -679,7 +800,7 @@ class Orchestrator {
|
|
|
679
800
|
if (value !== undefined) {
|
|
680
801
|
userDefinedKeys.add(dbKey);
|
|
681
802
|
}
|
|
682
|
-
if (value === undefined) {
|
|
803
|
+
if (value === undefined && !transformedFields.has(fieldName)) {
|
|
683
804
|
if (this.actualOperation === action_1.WriteOperation.Insert) {
|
|
684
805
|
if (field.defaultToViewerOnCreate && field.defaultValueOnCreate) {
|
|
685
806
|
throw new Error(`cannot set both defaultToViewerOnCreate and defaultValueOnCreate`);
|
|
@@ -718,6 +839,9 @@ class Orchestrator {
|
|
|
718
839
|
}
|
|
719
840
|
this.defaultFieldsByFieldName[fieldName] = defaultValue;
|
|
720
841
|
this.defaultFieldsByTSName[this.getInputKey(fieldName)] = defaultValue;
|
|
842
|
+
if (field.disableUserEditable || updateOnlyIfOther) {
|
|
843
|
+
defaultKeys.add(this.getInputKey(fieldName));
|
|
844
|
+
}
|
|
721
845
|
}
|
|
722
846
|
}
|
|
723
847
|
// if there's data changing, add data
|
|
@@ -728,7 +852,7 @@ class Orchestrator {
|
|
|
728
852
|
};
|
|
729
853
|
if (updateInput && this.options.updateInput) {
|
|
730
854
|
// this basically fixes #605. just needs to be exposed correctly
|
|
731
|
-
this.options.updateInput(this.defaultFieldsByTSName);
|
|
855
|
+
this.options.updateInput(this.defaultFieldsByTSName, this.actualOperation, defaultKeys);
|
|
732
856
|
}
|
|
733
857
|
}
|
|
734
858
|
return { data, userDefinedKeys };
|
|
@@ -764,7 +888,7 @@ class Orchestrator {
|
|
|
764
888
|
valid = await valid;
|
|
765
889
|
}
|
|
766
890
|
if (!valid) {
|
|
767
|
-
return
|
|
891
|
+
return invalidFieldError(fieldName, value);
|
|
768
892
|
}
|
|
769
893
|
}
|
|
770
894
|
// keep track of dependencies to resolve
|
|
@@ -779,7 +903,7 @@ class Orchestrator {
|
|
|
779
903
|
valid = await valid;
|
|
780
904
|
}
|
|
781
905
|
if (!valid) {
|
|
782
|
-
return
|
|
906
|
+
return invalidFieldError(fieldName, value);
|
|
783
907
|
}
|
|
784
908
|
}
|
|
785
909
|
if (field.format) {
|
|
@@ -823,6 +947,12 @@ class Orchestrator {
|
|
|
823
947
|
for (const fieldName of needsFullDataChecks) {
|
|
824
948
|
const field = schemaFields.get(fieldName);
|
|
825
949
|
let value = editedFields.get(fieldName);
|
|
950
|
+
// Deferred defaults are absent from editedFields, but validators must
|
|
951
|
+
// still receive the value that will be saved when this operation writes.
|
|
952
|
+
if (value === undefined &&
|
|
953
|
+
(op === action_1.WriteOperation.Insert || this.hasData(data))) {
|
|
954
|
+
value = this.defaultFieldsByFieldName[fieldName];
|
|
955
|
+
}
|
|
826
956
|
// @ts-ignore...
|
|
827
957
|
// type hackery because it's hard
|
|
828
958
|
const v = await field.validateWithFullData(value, this.options.builder);
|
|
@@ -855,6 +985,20 @@ class Orchestrator {
|
|
|
855
985
|
}
|
|
856
986
|
}
|
|
857
987
|
}
|
|
988
|
+
// If a trigger clears an input, the SQL write can still use its computed
|
|
989
|
+
// default. Update inverse edges for defaults included in `data`; apply edit
|
|
990
|
+
// defaults only when the edit has data to save.
|
|
991
|
+
for (const [fieldName, field] of this.fieldEdgeInputs) {
|
|
992
|
+
if (field.ids !== undefined ||
|
|
993
|
+
data[this.getStorageKey(fieldName)] === undefined) {
|
|
994
|
+
continue;
|
|
995
|
+
}
|
|
996
|
+
const value = this.defaultFieldsByFieldName[fieldName];
|
|
997
|
+
if (value === undefined) {
|
|
998
|
+
continue;
|
|
999
|
+
}
|
|
1000
|
+
this.__setFieldEdges(fieldName, value === null ? [] : Array.isArray(value) ? value : [value], field.edgeType, field.nodeType, {});
|
|
1001
|
+
}
|
|
858
1002
|
this.validatedFields = data;
|
|
859
1003
|
this.logValues = logValues;
|
|
860
1004
|
return errors;
|
package/action/privacy.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { Builder } from "./action";
|
|
2
2
|
import { Viewer, ID, Ent, PrivacyResult, PrivacyPolicyRule } from "../core/base";
|
|
3
|
+
export declare function isBuilder(node: ID | Builder<Ent, any>): node is Builder<Ent, any>;
|
|
3
4
|
export declare class DenyIfBuilder implements PrivacyPolicyRule {
|
|
4
5
|
private id?;
|
|
5
6
|
constructor(id?: (ID | Builder<Ent, any>) | undefined);
|
package/action/privacy.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.AllowIfBuilder = exports.DenyIfBuilder = void 0;
|
|
4
|
+
exports.isBuilder = isBuilder;
|
|
4
5
|
const base_1 = require("../core/base");
|
|
5
6
|
function isBuilder(node) {
|
|
6
7
|
return node.placeholderID !== undefined;
|
package/core/clause.js
CHANGED
|
@@ -185,7 +185,7 @@ class queryClause {
|
|
|
185
185
|
this.prefix = prefix;
|
|
186
186
|
}
|
|
187
187
|
clause(idx, alias) {
|
|
188
|
-
const q = (0, query_impl_1.buildQuery)(this.dependentQueryOptions);
|
|
188
|
+
const q = (0, query_impl_1.buildQuery)(this.dependentQueryOptions, idx);
|
|
189
189
|
return `${this.prefix} (${q})`;
|
|
190
190
|
}
|
|
191
191
|
columns() {
|
package/core/query_impl.d.ts
CHANGED
|
@@ -28,6 +28,6 @@ export declare function reverseOrderBy(orderby: OrderBy): OrderBy;
|
|
|
28
28
|
interface JoinInfo extends QueryFragmentInfo {
|
|
29
29
|
}
|
|
30
30
|
export declare function getJoinInfo(join: NonNullable<QueryableDataOptions["join"]>, clauseIdx?: number): JoinInfo;
|
|
31
|
-
export declare function buildQueryData(options: QueryableDataOptions): BuiltQueryData;
|
|
32
|
-
export declare function buildQuery(options: QueryableDataOptions): string;
|
|
31
|
+
export declare function buildQueryData(options: QueryableDataOptions, startIdx?: number): BuiltQueryData;
|
|
32
|
+
export declare function buildQuery(options: QueryableDataOptions, startIdx?: number): string;
|
|
33
33
|
export {};
|
package/core/query_impl.js
CHANGED
|
@@ -171,12 +171,14 @@ function getJoinInfo(join, clauseIdx = 1) {
|
|
|
171
171
|
logValues,
|
|
172
172
|
};
|
|
173
173
|
}
|
|
174
|
-
|
|
174
|
+
// startIdx is the first placeholder number when embedding this query in another.
|
|
175
|
+
// The returned values contain only this query's parameters.
|
|
176
|
+
function buildQueryData(options, startIdx = 1) {
|
|
175
177
|
const fieldsAlias = options.fieldsAlias ?? options.alias;
|
|
176
|
-
const fieldInfo = getFieldsInfo(options.fields, fieldsAlias, options.disableFieldsAlias,
|
|
178
|
+
const fieldInfo = getFieldsInfo(options.fields, fieldsAlias, options.disableFieldsAlias, startIdx);
|
|
177
179
|
const values = [...fieldInfo.values];
|
|
178
180
|
const logValues = [...fieldInfo.logValues];
|
|
179
|
-
let clauseIdx =
|
|
181
|
+
let clauseIdx = startIdx + fieldInfo.valuesUsed;
|
|
180
182
|
const parts = [];
|
|
181
183
|
const tableName = options.alias
|
|
182
184
|
? `${options.tableName} AS ${options.alias}`
|
|
@@ -220,6 +222,6 @@ function buildQueryData(options) {
|
|
|
220
222
|
logValues,
|
|
221
223
|
};
|
|
222
224
|
}
|
|
223
|
-
function buildQuery(options) {
|
|
224
|
-
return buildQueryData(options).query;
|
|
225
|
+
function buildQuery(options, startIdx = 1) {
|
|
226
|
+
return buildQueryData(options, startIdx).query;
|
|
225
227
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@snowtop/ent",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.15",
|
|
4
4
|
"description": "snowtop ent framework",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"tsconfig-paths": "4.2.0",
|
|
19
19
|
"tslib": "2.8.1",
|
|
20
20
|
"typescript": "5.9.3",
|
|
21
|
-
"uuid": "
|
|
21
|
+
"uuid": "11.1.1"
|
|
22
22
|
},
|
|
23
23
|
"peerDependencies": {
|
|
24
24
|
"@swc-node/register": "1.6.8",
|
package/schema/base_schema.js
CHANGED
|
@@ -3,26 +3,31 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.BaseEntSchemaWithTZ = exports.BaseEntSchema = exports.EntSchemaWithTZ = exports.EntSchema = exports.Node = exports.Timestamps = void 0;
|
|
4
4
|
const uuid_1 = require("uuid");
|
|
5
5
|
const field_1 = require("./field");
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
6
|
+
function timestampFields(withTimezone) {
|
|
7
|
+
return {
|
|
8
|
+
createdAt: (0, field_1.TimestampType)({
|
|
9
|
+
withTimezone,
|
|
10
|
+
hideFromGraphQL: true,
|
|
11
|
+
disableUserEditable: true,
|
|
12
|
+
defaultValueOnCreate: () => {
|
|
13
|
+
return new Date();
|
|
14
|
+
},
|
|
15
|
+
}),
|
|
16
|
+
updatedAt: (0, field_1.TimestampType)({
|
|
17
|
+
withTimezone,
|
|
18
|
+
hideFromGraphQL: true,
|
|
19
|
+
disableUserEditable: true,
|
|
20
|
+
defaultValueOnCreate: () => {
|
|
21
|
+
return new Date();
|
|
22
|
+
},
|
|
23
|
+
onlyUpdateIfOtherFieldsBeingSet_BETA: true,
|
|
24
|
+
defaultValueOnEdit: () => {
|
|
25
|
+
return new Date();
|
|
26
|
+
},
|
|
27
|
+
}),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
let tsFields = timestampFields(false);
|
|
26
31
|
// Timestamps is a Pattern that adds a createdAt and updatedAt timestamp fields to the ent
|
|
27
32
|
exports.Timestamps = {
|
|
28
33
|
name: "timestamps",
|
|
@@ -43,25 +48,7 @@ let nodeFields = {
|
|
|
43
48
|
let nodeFieldsWithTZ = {
|
|
44
49
|
// inconsistent naming :(
|
|
45
50
|
id: nodeField,
|
|
46
|
-
|
|
47
|
-
hideFromGraphQL: true,
|
|
48
|
-
disableUserEditable: true,
|
|
49
|
-
defaultValueOnCreate: () => {
|
|
50
|
-
return new Date();
|
|
51
|
-
},
|
|
52
|
-
withTimezone: true,
|
|
53
|
-
}),
|
|
54
|
-
updatedAt: (0, field_1.TimestampType)({
|
|
55
|
-
hideFromGraphQL: true,
|
|
56
|
-
disableUserEditable: true,
|
|
57
|
-
defaultValueOnCreate: () => {
|
|
58
|
-
return new Date();
|
|
59
|
-
},
|
|
60
|
-
defaultValueOnEdit: () => {
|
|
61
|
-
return new Date();
|
|
62
|
-
},
|
|
63
|
-
withTimezone: true,
|
|
64
|
-
}),
|
|
51
|
+
...timestampFields(true),
|
|
65
52
|
};
|
|
66
53
|
// Node is a Pattern that adds 3 fields to the ent: (id, createdAt, and updatedAt timestamps)
|
|
67
54
|
exports.Node = {
|
package/schema/struct_field.js
CHANGED
|
@@ -35,7 +35,7 @@ class StructField extends field_1.BaseField {
|
|
|
35
35
|
}
|
|
36
36
|
}
|
|
37
37
|
formatImpl(obj, nested) {
|
|
38
|
-
if (
|
|
38
|
+
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
|
|
39
39
|
throw new Error("valid was not called");
|
|
40
40
|
}
|
|
41
41
|
let ret = {};
|
|
@@ -122,7 +122,12 @@ class StructField extends field_1.BaseField {
|
|
|
122
122
|
return this.formatImpl(obj, nested);
|
|
123
123
|
}
|
|
124
124
|
async validImpl(obj) {
|
|
125
|
-
|
|
125
|
+
// GraphQL.js valueFromAST turns inline objects such as prefs: {enabled: true}
|
|
126
|
+
// into null-prototype records, which fail instanceof Object. Objects supplied
|
|
127
|
+
// in the JSON variables payload use ordinary prototypes instead. A struct
|
|
128
|
+
// can still be inline when other values use variables; object-valued variable
|
|
129
|
+
// defaults also go through valueFromAST when the variable is omitted.
|
|
130
|
+
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
|
|
126
131
|
return false;
|
|
127
132
|
}
|
|
128
133
|
let promises = [];
|
|
@@ -241,9 +246,6 @@ class StructField extends field_1.BaseField {
|
|
|
241
246
|
}));
|
|
242
247
|
return valid.every((b) => b);
|
|
243
248
|
}
|
|
244
|
-
if (!(obj instanceof Object)) {
|
|
245
|
-
return false;
|
|
246
|
-
}
|
|
247
249
|
return this.validImpl(obj);
|
|
248
250
|
}
|
|
249
251
|
}
|
package/schema/union_field.js
CHANGED
|
@@ -25,7 +25,7 @@ class UnionField extends field_1.BaseField {
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
format(obj) {
|
|
28
|
-
if (
|
|
28
|
+
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
|
|
29
29
|
throw new Error("valid was not called");
|
|
30
30
|
}
|
|
31
31
|
const k = obj[KEY];
|
|
@@ -46,7 +46,8 @@ class UnionField extends field_1.BaseField {
|
|
|
46
46
|
};
|
|
47
47
|
}
|
|
48
48
|
async valid(obj) {
|
|
49
|
-
|
|
49
|
+
// Accept the same record shapes as the member struct fields.
|
|
50
|
+
if (typeof obj !== "object" || obj === null || Array.isArray(obj)) {
|
|
50
51
|
return false;
|
|
51
52
|
}
|
|
52
53
|
let promises = [];
|
|
@@ -533,7 +533,10 @@ async function main() {
|
|
|
533
533
|
});
|
|
534
534
|
}
|
|
535
535
|
main()
|
|
536
|
-
.
|
|
536
|
+
// Imported application modules can leave database pools or other handles open.
|
|
537
|
+
// The awaited stdout write above is the script's final required work, so exit
|
|
538
|
+
// explicitly instead of waiting for the event loop to drain.
|
|
539
|
+
.then(() => (0, process_1.exit)(0))
|
|
537
540
|
.catch((err) => {
|
|
538
541
|
console.error(err);
|
|
539
542
|
(0, process_1.exit)(1);
|
package/testutils/builder.d.ts
CHANGED
|
@@ -81,6 +81,7 @@ export declare class SimpleBuilder<T extends Ent, TExistingEnt extends TMaybleNu
|
|
|
81
81
|
placeholderID: ID;
|
|
82
82
|
orchestrator: Orchestrator<T, Data, Viewer, TExistingEnt>;
|
|
83
83
|
fields: Map<string, any>;
|
|
84
|
+
private defaultInput;
|
|
84
85
|
nodeType: string;
|
|
85
86
|
m: Map<string, any>;
|
|
86
87
|
constructor(viewer: Viewer, schema: BuilderSchema<T>, fields: Map<string, any>, operation: WriteOperation | undefined, existingEnt: TExistingEnt, action?: Action<T, SimpleBuilder<T, TExistingEnt>, Viewer, Data, TExistingEnt> | undefined, expressions?: Map<string, Clause>);
|
package/testutils/builder.js
CHANGED
|
@@ -191,6 +191,7 @@ class SimpleBuilder {
|
|
|
191
191
|
this.schema = schema;
|
|
192
192
|
this.operation = operation;
|
|
193
193
|
this.existingEnt = existingEnt;
|
|
194
|
+
this.defaultInput = new Map();
|
|
194
195
|
this.m = new Map();
|
|
195
196
|
// create dynamic placeholder
|
|
196
197
|
// TODO: do we need to use this as the node when there's an existingEnt
|
|
@@ -247,11 +248,18 @@ class SimpleBuilder {
|
|
|
247
248
|
// to simulate what we do in generated builders where we return a new Map
|
|
248
249
|
const m = new Map();
|
|
249
250
|
for (const [k, v] of this.fields) {
|
|
250
|
-
|
|
251
|
+
if (!this.defaultInput.has(k) || this.defaultInput.get(k) !== v) {
|
|
252
|
+
m.set(k, v);
|
|
253
|
+
}
|
|
251
254
|
}
|
|
252
255
|
return m;
|
|
253
256
|
},
|
|
254
|
-
updateInput:
|
|
257
|
+
updateInput: (input, _operation, defaultKeys) => {
|
|
258
|
+
this.updateInput(input);
|
|
259
|
+
for (const key of defaultKeys ?? []) {
|
|
260
|
+
this.defaultInput.set(key, input[key]);
|
|
261
|
+
}
|
|
262
|
+
},
|
|
255
263
|
});
|
|
256
264
|
}
|
|
257
265
|
getInput() {
|
|
@@ -264,6 +272,7 @@ class SimpleBuilder {
|
|
|
264
272
|
updateInput(input) {
|
|
265
273
|
const knownFields = (0, schema_1.getFields)(this.schema);
|
|
266
274
|
for (const k in input) {
|
|
275
|
+
this.defaultInput.delete(k);
|
|
267
276
|
if (knownFields.has(k)) {
|
|
268
277
|
this.fields.set(k, input[k]);
|
|
269
278
|
}
|
package/testutils/db/temp_db.js
CHANGED
|
@@ -627,13 +627,14 @@ function setupSqlite(connString, tables, opts) {
|
|
|
627
627
|
const client = await db_1.default.getInstance().getNewClient();
|
|
628
628
|
for (const [key, _] of tdb.__getTables()) {
|
|
629
629
|
const query = `delete from ${key}`;
|
|
630
|
-
if (isSyncClient(client))
|
|
630
|
+
if (isSyncClient(client)) {
|
|
631
631
|
if (client.execSync) {
|
|
632
632
|
client.execSync(query);
|
|
633
633
|
}
|
|
634
634
|
else {
|
|
635
635
|
await client.exec(query);
|
|
636
636
|
}
|
|
637
|
+
}
|
|
637
638
|
}
|
|
638
639
|
});
|
|
639
640
|
}
|