@snowtop/ent 0.1.0-alpha80 → 0.1.0-alpha86
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/action.d.ts +10 -7
- package/action/action.js +2 -6
- package/action/orchestrator.d.ts +1 -0
- package/action/orchestrator.js +23 -0
- package/core/base.d.ts +10 -5
- package/core/base.js +7 -1
- package/core/context.d.ts +3 -3
- package/core/context.js +18 -5
- package/core/ent.d.ts +1 -1
- package/core/ent.js +161 -147
- package/core/loaders/object_loader.d.ts +2 -2
- package/core/loaders/object_loader.js +45 -58
- package/package.json +1 -1
- package/schema/field.d.ts +10 -3
- package/schema/field.js +57 -10
- package/schema/schema.d.ts +1 -0
- package/schema/struct_field.js +1 -1
- package/scripts/custom_graphql.js +2 -2
- package/scripts/migrate_v0.1.js +1 -1
- package/testutils/builder.js +2 -1
package/action/action.d.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
import { Ent, EntConstructor, Viewer, ID, Data, PrivacyPolicy, Context } from "../core/base";
|
|
1
|
+
import { Ent, EntConstructor, Viewer, ID, Data, PrivacyPolicy, Context, WriteOperation } from "../core/base";
|
|
2
2
|
import { DataOperation, AssocEdgeInputOptions } from "../core/ent";
|
|
3
3
|
import { Queryer } from "../core/db";
|
|
4
4
|
import { TransformedUpdateOperation, UpdateOperation } from "../schema";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
Edit = "edit",
|
|
8
|
-
Delete = "delete"
|
|
9
|
-
}
|
|
5
|
+
import { FieldInfoMap } from "../schema/schema";
|
|
6
|
+
export { WriteOperation };
|
|
10
7
|
declare type MaybeNull<T extends Ent> = T | null;
|
|
11
8
|
declare type TMaybleNullableEnt<T extends Ent> = T | MaybeNull<T>;
|
|
9
|
+
interface BuilderOrchestrator {
|
|
10
|
+
__getOptions(): {
|
|
11
|
+
fieldInfo: FieldInfoMap;
|
|
12
|
+
};
|
|
13
|
+
}
|
|
12
14
|
export interface Builder<TEnt extends Ent<TViewer>, TViewer extends Viewer = Viewer, TExistingEnt extends TMaybleNullableEnt<TEnt> = MaybeNull<TEnt>> {
|
|
13
15
|
existingEnt: TExistingEnt;
|
|
14
16
|
ent: EntConstructor<TEnt, TViewer>;
|
|
@@ -18,6 +20,8 @@ export interface Builder<TEnt extends Ent<TViewer>, TViewer extends Viewer = Vie
|
|
|
18
20
|
operation: WriteOperation;
|
|
19
21
|
editedEnt?(): Promise<TEnt | null>;
|
|
20
22
|
nodeType: string;
|
|
23
|
+
getInput(): Data;
|
|
24
|
+
orchestrator: BuilderOrchestrator;
|
|
21
25
|
}
|
|
22
26
|
export interface Executor extends Iterable<DataOperation>, Iterator<DataOperation> {
|
|
23
27
|
placeholderID: ID;
|
|
@@ -66,4 +70,3 @@ interface Orchestrator {
|
|
|
66
70
|
viewer: Viewer;
|
|
67
71
|
}
|
|
68
72
|
export declare function setEdgeTypeInGroup<T extends string>(orchestrator: Orchestrator, inputEnumValue: string, id1: ID, id2: ID, nodeType: string, m: Map<T, string>): Promise<void>;
|
|
69
|
-
export {};
|
package/action/action.js
CHANGED
|
@@ -1,14 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.setEdgeTypeInGroup = exports.saveBuilderX = exports.saveBuilder = exports.WriteOperation = void 0;
|
|
4
|
+
const base_1 = require("../core/base");
|
|
5
|
+
Object.defineProperty(exports, "WriteOperation", { enumerable: true, get: function () { return base_1.WriteOperation; } });
|
|
4
6
|
const ent_1 = require("../core/ent");
|
|
5
7
|
const logger_1 = require("../core/logger");
|
|
6
|
-
var WriteOperation;
|
|
7
|
-
(function (WriteOperation) {
|
|
8
|
-
WriteOperation["Insert"] = "insert";
|
|
9
|
-
WriteOperation["Edit"] = "edit";
|
|
10
|
-
WriteOperation["Delete"] = "delete";
|
|
11
|
-
})(WriteOperation = exports.WriteOperation || (exports.WriteOperation = {}));
|
|
12
8
|
async function saveBuilder(builder) {
|
|
13
9
|
await saveBuilderImpl(builder, false);
|
|
14
10
|
}
|
package/action/orchestrator.d.ts
CHANGED
|
@@ -49,6 +49,7 @@ export declare class Orchestrator<TEnt extends Ent<TViewer>, TInput extends Data
|
|
|
49
49
|
private disableTransformations;
|
|
50
50
|
private memoizedGetFields;
|
|
51
51
|
constructor(options: OrchestratorOptions<TEnt, TInput, TViewer, TExistingEnt>);
|
|
52
|
+
__getOptions(): OrchestratorOptions<any, any, any, any>;
|
|
52
53
|
private addEdge;
|
|
53
54
|
setDisableTransformations(val: boolean): void;
|
|
54
55
|
addInboundEdge<T2 extends Ent>(id1: ID | Builder<T2, any>, edgeType: string, nodeType: string, options?: AssocEdgeInputOptions): void;
|
package/action/orchestrator.js
CHANGED
|
@@ -88,6 +88,10 @@ class Orchestrator {
|
|
|
88
88
|
this.existingEnt = this.options.builder.existingEnt;
|
|
89
89
|
this.memoizedGetFields = (0, memoizee_1.default)(this.getFieldsInfo.bind(this));
|
|
90
90
|
}
|
|
91
|
+
// don't type this because we don't care
|
|
92
|
+
__getOptions() {
|
|
93
|
+
return this.options;
|
|
94
|
+
}
|
|
91
95
|
addEdge(edge, op) {
|
|
92
96
|
this.edgeSet.add(edge.edgeType);
|
|
93
97
|
let m1 = this.edges.get(edge.edgeType) || new Map();
|
|
@@ -607,8 +611,12 @@ class Orchestrator {
|
|
|
607
611
|
// build up data to be saved...
|
|
608
612
|
let data = {};
|
|
609
613
|
let logValues = {};
|
|
614
|
+
let needsFullDataChecks = [];
|
|
610
615
|
for (const [fieldName, field] of schemaFields) {
|
|
611
616
|
let value = editedFields.get(fieldName);
|
|
617
|
+
if (field.validateWithFullData) {
|
|
618
|
+
needsFullDataChecks.push(fieldName);
|
|
619
|
+
}
|
|
612
620
|
if (value === undefined && op === action_1.WriteOperation.Insert) {
|
|
613
621
|
// null allowed
|
|
614
622
|
value = this.defaultFieldsByFieldName[fieldName];
|
|
@@ -626,6 +634,21 @@ class Orchestrator {
|
|
|
626
634
|
logValues[dbKey] = field.logValue(value);
|
|
627
635
|
}
|
|
628
636
|
}
|
|
637
|
+
for (const fieldName of needsFullDataChecks) {
|
|
638
|
+
const field = schemaFields.get(fieldName);
|
|
639
|
+
let value = editedFields.get(fieldName);
|
|
640
|
+
// @ts-ignore...
|
|
641
|
+
// type hackery because it's hard
|
|
642
|
+
const v = await field.validateWithFullData(value, this.options.builder);
|
|
643
|
+
if (!v) {
|
|
644
|
+
if (value === undefined) {
|
|
645
|
+
errors.push(new Error(`field ${fieldName} set to undefined when it can't be nullable`));
|
|
646
|
+
}
|
|
647
|
+
else {
|
|
648
|
+
errors.push(new Error(`field ${fieldName} set to null when it can't be nullable`));
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
629
652
|
// we ignored default values while editing.
|
|
630
653
|
// if we're editing and there's data, add default values
|
|
631
654
|
if (op === action_1.WriteOperation.Edit && this.hasData(data)) {
|
package/core/base.d.ts
CHANGED
|
@@ -5,12 +5,12 @@ export interface Loader<T, V> {
|
|
|
5
5
|
loadMany?(keys: T[]): Promise<(V | null)[]>;
|
|
6
6
|
clearAll(): any;
|
|
7
7
|
}
|
|
8
|
-
interface LoaderWithLoadMany<T, V> extends Loader<T, V> {
|
|
9
|
-
loadMany(keys: T[]): Promise<
|
|
8
|
+
export interface LoaderWithLoadMany<T, V> extends Loader<T, V> {
|
|
9
|
+
loadMany(keys: T[]): Promise<V[]>;
|
|
10
10
|
}
|
|
11
|
-
export interface LoaderFactory<
|
|
11
|
+
export interface LoaderFactory<K, V> {
|
|
12
12
|
name: string;
|
|
13
|
-
createLoader(context?: Context): Loader<
|
|
13
|
+
createLoader(context?: Context): Loader<K, V>;
|
|
14
14
|
}
|
|
15
15
|
interface LoaderFactoryWithLoaderMany<T, V> extends LoaderFactory<T, V> {
|
|
16
16
|
createLoader(context?: Context): LoaderWithLoadMany<T, V>;
|
|
@@ -25,12 +25,12 @@ export interface PrimableLoader<T, V> extends Loader<T, V> {
|
|
|
25
25
|
}
|
|
26
26
|
interface cache {
|
|
27
27
|
getLoader<T, V>(name: string, create: () => Loader<T, V>): Loader<T, V>;
|
|
28
|
+
getLoaderWithLoadMany<T, V>(name: string, create: () => LoaderWithLoadMany<T, V>): LoaderWithLoadMany<T, V>;
|
|
28
29
|
getCachedRows(options: queryOptions): Data[] | null;
|
|
29
30
|
getCachedRow(options: queryOptions): Data | null;
|
|
30
31
|
primeCache(options: queryOptions, rows: Data[]): void;
|
|
31
32
|
primeCache(options: queryOptions, rows: Data): void;
|
|
32
33
|
clearCache(): void;
|
|
33
|
-
getEntCache(): Map<string, Ent | Error | null>;
|
|
34
34
|
}
|
|
35
35
|
interface queryOptions {
|
|
36
36
|
fields: string[];
|
|
@@ -143,4 +143,9 @@ export interface PrivacyPolicyRule<TEnt extends Ent = Ent, TViewer = Viewer> {
|
|
|
143
143
|
export interface PrivacyPolicy<TEnt extends Ent = Ent, TViewer = Viewer> {
|
|
144
144
|
rules: PrivacyPolicyRule<TEnt, TViewer>[];
|
|
145
145
|
}
|
|
146
|
+
export declare enum WriteOperation {
|
|
147
|
+
Insert = "insert",
|
|
148
|
+
Edit = "edit",
|
|
149
|
+
Delete = "delete"
|
|
150
|
+
}
|
|
146
151
|
export {};
|
package/core/base.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.DenyWithReason = exports.Deny = exports.Skip = exports.Allow = void 0;
|
|
3
|
+
exports.WriteOperation = exports.DenyWithReason = exports.Deny = exports.Skip = exports.Allow = void 0;
|
|
4
4
|
// Privacy
|
|
5
5
|
var privacyResult;
|
|
6
6
|
(function (privacyResult) {
|
|
@@ -53,3 +53,9 @@ function DenyWithReason(e) {
|
|
|
53
53
|
};
|
|
54
54
|
}
|
|
55
55
|
exports.DenyWithReason = DenyWithReason;
|
|
56
|
+
var WriteOperation;
|
|
57
|
+
(function (WriteOperation) {
|
|
58
|
+
WriteOperation["Insert"] = "insert";
|
|
59
|
+
WriteOperation["Edit"] = "edit";
|
|
60
|
+
WriteOperation["Delete"] = "delete";
|
|
61
|
+
})(WriteOperation = exports.WriteOperation || (exports.WriteOperation = {}));
|
package/core/context.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/// <reference types="node" />
|
|
2
|
-
import { Viewer, Data, Loader,
|
|
2
|
+
import { Viewer, Data, Loader, LoaderWithLoadMany } from "./base";
|
|
3
3
|
import { IncomingMessage, ServerResponse } from "http";
|
|
4
4
|
import * as clause from "./clause";
|
|
5
5
|
import { Context } from "./base";
|
|
@@ -11,17 +11,17 @@ export interface RequestContext<TViewer extends Viewer = Viewer> extends Context
|
|
|
11
11
|
}
|
|
12
12
|
export declare class ContextCache {
|
|
13
13
|
loaders: Map<string, Loader<any, any>>;
|
|
14
|
+
loaderWithLoadMany: Map<string, LoaderWithLoadMany<any, any>>;
|
|
14
15
|
getLoader<T, V>(name: string, create: () => Loader<T, V>): Loader<T, V>;
|
|
16
|
+
getLoaderWithLoadMany<T, V>(name: string, create: () => LoaderWithLoadMany<T, V>): LoaderWithLoadMany<T, V>;
|
|
15
17
|
private itemMap;
|
|
16
18
|
private listMap;
|
|
17
|
-
private entCache;
|
|
18
19
|
private getkey;
|
|
19
20
|
getCachedRows(options: queryOptions): Data[] | null;
|
|
20
21
|
getCachedRow(options: queryOptions): Data | null;
|
|
21
22
|
primeCache(options: queryOptions, rows: Data[]): void;
|
|
22
23
|
primeCache(options: queryOptions, rows: Data): void;
|
|
23
24
|
clearCache(): void;
|
|
24
|
-
getEntCache(): Map<string, Error | Ent<any> | null>;
|
|
25
25
|
}
|
|
26
26
|
interface queryOptions {
|
|
27
27
|
fields: string[];
|
package/core/context.js
CHANGED
|
@@ -5,10 +5,11 @@ const logger_1 = require("./logger");
|
|
|
5
5
|
class ContextCache {
|
|
6
6
|
constructor() {
|
|
7
7
|
this.loaders = new Map();
|
|
8
|
+
// we should eventually combine the two but better for typing to be separate for now
|
|
9
|
+
this.loaderWithLoadMany = new Map();
|
|
8
10
|
// we have a per-table map to make it easier to purge and have less things to compare with
|
|
9
11
|
this.itemMap = new Map();
|
|
10
12
|
this.listMap = new Map();
|
|
11
|
-
this.entCache = new Map();
|
|
12
13
|
}
|
|
13
14
|
getLoader(name, create) {
|
|
14
15
|
let l = this.loaders.get(name);
|
|
@@ -20,6 +21,16 @@ class ContextCache {
|
|
|
20
21
|
this.loaders.set(name, l);
|
|
21
22
|
return l;
|
|
22
23
|
}
|
|
24
|
+
getLoaderWithLoadMany(name, create) {
|
|
25
|
+
let l = this.loaderWithLoadMany.get(name);
|
|
26
|
+
if (l) {
|
|
27
|
+
return l;
|
|
28
|
+
}
|
|
29
|
+
(0, logger_1.log)("debug", `new context-aware loader created for ${name}`);
|
|
30
|
+
l = create();
|
|
31
|
+
this.loaderWithLoadMany.set(name, l);
|
|
32
|
+
return l;
|
|
33
|
+
}
|
|
23
34
|
// tableName is ignored bcos already indexed on that
|
|
24
35
|
// maybe we just want to store sql queries???
|
|
25
36
|
getkey(options) {
|
|
@@ -80,13 +91,15 @@ class ContextCache {
|
|
|
80
91
|
// but may have some benefits by explicitily doing so?
|
|
81
92
|
loader.clearAll();
|
|
82
93
|
}
|
|
94
|
+
for (const [_key, loader] of this.loaderWithLoadMany) {
|
|
95
|
+
// may not need this since we're clearing the loaders themselves...
|
|
96
|
+
// but may have some benefits by explicitily doing so?
|
|
97
|
+
loader.clearAll();
|
|
98
|
+
}
|
|
83
99
|
this.loaders.clear();
|
|
100
|
+
this.loaderWithLoadMany.clear();
|
|
84
101
|
this.itemMap.clear();
|
|
85
102
|
this.listMap.clear();
|
|
86
|
-
this.entCache.clear();
|
|
87
|
-
}
|
|
88
|
-
getEntCache() {
|
|
89
|
-
return this.entCache;
|
|
90
103
|
}
|
|
91
104
|
}
|
|
92
105
|
exports.ContextCache = ContextCache;
|
package/core/ent.d.ts
CHANGED
|
@@ -225,7 +225,7 @@ interface loadEdgeForIDOptions<T extends AssocEdge> extends loadCustomEdgesOptio
|
|
|
225
225
|
}
|
|
226
226
|
export declare function loadEdgeForID2<T extends AssocEdge>(options: loadEdgeForIDOptions<T>): Promise<T | undefined>;
|
|
227
227
|
export declare function loadNodesByEdge<T extends Ent>(viewer: Viewer, id1: ID, edgeType: string, options: LoadEntOptions<T>): Promise<T[]>;
|
|
228
|
-
export declare function applyPrivacyPolicyForRow<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, options: LoadEntOptions<TEnt, TViewer>, row: Data
|
|
228
|
+
export declare function applyPrivacyPolicyForRow<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, options: LoadEntOptions<TEnt, TViewer>, row: Data): Promise<TEnt | null>;
|
|
229
229
|
export declare function applyPrivacyPolicyForRows<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, rows: Data[], options: LoadEntOptions<TEnt, TViewer>): Promise<Map<ID, TEnt>>;
|
|
230
230
|
export declare function getEdgeTypeInGroup<T extends string>(viewer: Viewer, id1: ID, id2: ID, m: Map<T, string>): Promise<[T, AssocEdge] | undefined>;
|
|
231
231
|
export {};
|
package/core/ent.js
CHANGED
|
@@ -57,6 +57,34 @@ class cacheMap {
|
|
|
57
57
|
return this.m.clear();
|
|
58
58
|
}
|
|
59
59
|
}
|
|
60
|
+
class entCacheMap {
|
|
61
|
+
constructor(viewer, options) {
|
|
62
|
+
this.viewer = viewer;
|
|
63
|
+
this.options = options;
|
|
64
|
+
this.m = new Map();
|
|
65
|
+
this.logEnabled = false;
|
|
66
|
+
this.logEnabled = (0, logger_1.logEnabled)("cache");
|
|
67
|
+
}
|
|
68
|
+
get(id) {
|
|
69
|
+
const ret = this.m.get(id);
|
|
70
|
+
if (this.logEnabled && ret) {
|
|
71
|
+
const key = getEntKey(this.viewer, id, this.options);
|
|
72
|
+
(0, logger_1.log)("cache", {
|
|
73
|
+
"ent-cache-hit": key,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
return ret;
|
|
77
|
+
}
|
|
78
|
+
set(key, value) {
|
|
79
|
+
return this.m.set(key, value);
|
|
80
|
+
}
|
|
81
|
+
delete(key) {
|
|
82
|
+
return this.m.delete(key);
|
|
83
|
+
}
|
|
84
|
+
clear() {
|
|
85
|
+
return this.m.clear();
|
|
86
|
+
}
|
|
87
|
+
}
|
|
60
88
|
function createDataLoader(options) {
|
|
61
89
|
const loaderOptions = {};
|
|
62
90
|
// if query logging is enabled, we should log what's happening with loader
|
|
@@ -87,60 +115,116 @@ function createDataLoader(options) {
|
|
|
87
115
|
return result;
|
|
88
116
|
}, loaderOptions);
|
|
89
117
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
function entFromCacheMaybe(viewer, id, options) {
|
|
96
|
-
const cache = viewer.context?.cache?.getEntCache();
|
|
97
|
-
if (!cache) {
|
|
98
|
-
return {};
|
|
99
|
-
}
|
|
100
|
-
const key = getEntKey(viewer, id, options);
|
|
101
|
-
const r = cache.get(key);
|
|
102
|
-
if (r !== undefined) {
|
|
103
|
-
(0, logger_1.log)("cache", {
|
|
104
|
-
"ent-cache-hit": key,
|
|
105
|
-
});
|
|
118
|
+
// used to wrap errors that would eventually be thrown in ents
|
|
119
|
+
// not an Error because DataLoader automatically rejects that
|
|
120
|
+
class ErrorWrapper {
|
|
121
|
+
constructor(error) {
|
|
122
|
+
this.error = error;
|
|
106
123
|
}
|
|
107
|
-
return {
|
|
108
|
-
key,
|
|
109
|
-
ent: r instanceof Error ? undefined : r,
|
|
110
|
-
error: r instanceof Error ? r : undefined,
|
|
111
|
-
cache,
|
|
112
|
-
};
|
|
113
124
|
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
const
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
125
|
+
function createEntLoader(viewer, options, map) {
|
|
126
|
+
// share the cache across loaders even if we create a new instance
|
|
127
|
+
const loaderOptions = {};
|
|
128
|
+
loaderOptions.cacheMap = map;
|
|
129
|
+
return new dataloader_1.default(async (ids) => {
|
|
130
|
+
if (!ids.length) {
|
|
131
|
+
return [];
|
|
132
|
+
}
|
|
133
|
+
let result = [];
|
|
134
|
+
const loader = options.loaderFactory.createLoader(viewer.context);
|
|
135
|
+
const rows = await loader.loadMany(ids);
|
|
136
|
+
// this is a loader which should return the same order based on passed-in ids
|
|
137
|
+
// so let's depend on that...
|
|
138
|
+
for (let idx = 0; idx < rows.length; idx++) {
|
|
139
|
+
const row = rows[idx];
|
|
140
|
+
// db error
|
|
141
|
+
if (row instanceof Error) {
|
|
142
|
+
result[idx] = row;
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
else if (!row) {
|
|
146
|
+
result[idx] = new ErrorWrapper(new Error(`couldn't find row for value ${ids[idx]}`));
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
const r = await applyPrivacyPolicyForRowImpl(viewer, options, row);
|
|
150
|
+
if (r instanceof Error) {
|
|
151
|
+
result[idx] = new ErrorWrapper(r);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
result[idx] = r;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
return result;
|
|
159
|
+
}, loaderOptions);
|
|
121
160
|
}
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
161
|
+
class EntLoader {
|
|
162
|
+
constructor(viewer, options) {
|
|
163
|
+
this.viewer = viewer;
|
|
164
|
+
this.options = options;
|
|
165
|
+
this.map = new entCacheMap(viewer, options);
|
|
166
|
+
this.loader = createEntLoader(this.viewer, this.options, this.map);
|
|
126
167
|
}
|
|
127
|
-
|
|
128
|
-
|
|
168
|
+
getMap() {
|
|
169
|
+
return this.map;
|
|
129
170
|
}
|
|
130
|
-
|
|
131
|
-
|
|
171
|
+
async load(id) {
|
|
172
|
+
return this.loader.load(id);
|
|
173
|
+
}
|
|
174
|
+
async loadMany(ids) {
|
|
175
|
+
return this.loader.loadMany(ids);
|
|
176
|
+
}
|
|
177
|
+
prime(id, ent) {
|
|
178
|
+
this.loader.prime(id, ent);
|
|
179
|
+
}
|
|
180
|
+
clear(id) {
|
|
181
|
+
this.loader.clear(id);
|
|
182
|
+
}
|
|
183
|
+
clearAll() {
|
|
184
|
+
this.loader.clearAll();
|
|
132
185
|
}
|
|
133
|
-
return ent;
|
|
134
186
|
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
return info.ent;
|
|
187
|
+
function getEntLoader(viewer, options) {
|
|
188
|
+
if (!viewer.context?.cache) {
|
|
189
|
+
return new EntLoader(viewer, options);
|
|
139
190
|
}
|
|
140
|
-
const
|
|
141
|
-
return
|
|
191
|
+
const name = `ent-loader:${viewer.instanceKey()}:${options.loaderFactory.name}`;
|
|
192
|
+
return viewer.context.cache.getLoaderWithLoadMany(name, () => new EntLoader(viewer, options));
|
|
193
|
+
}
|
|
194
|
+
function getEntKey(viewer, id, options) {
|
|
195
|
+
return `${viewer.instanceKey()}:${options.loaderFactory.name}:${id}`;
|
|
196
|
+
}
|
|
197
|
+
exports.getEntKey = getEntKey;
|
|
198
|
+
async function loadEnt(viewer, id, options) {
|
|
199
|
+
const r = await getEntLoader(viewer, options).load(id);
|
|
200
|
+
return r instanceof ErrorWrapper ? null : r;
|
|
142
201
|
}
|
|
143
202
|
exports.loadEnt = loadEnt;
|
|
203
|
+
async function applyPrivacyPolicyForRowAndStoreInEntLoader(viewer, row, options,
|
|
204
|
+
// can pass in loader when calling this for multi-id cases...
|
|
205
|
+
loader) {
|
|
206
|
+
if (!loader) {
|
|
207
|
+
loader = getEntLoader(viewer, options);
|
|
208
|
+
}
|
|
209
|
+
// TODO every row.id needs to be audited...
|
|
210
|
+
// https://github.com/lolopinto/ent/issues/1064
|
|
211
|
+
const id = row.id;
|
|
212
|
+
// we should check the ent loader cache to see if this is already there
|
|
213
|
+
// TODO hmm... we eventually need a custom data-loader for this too so that it's all done correctly if there's a complicated fetch deep down in graphql
|
|
214
|
+
const result = loader.getMap().get(id);
|
|
215
|
+
if (result !== undefined) {
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
const r = await applyPrivacyPolicyForRowImpl(viewer, options, row);
|
|
219
|
+
if (r instanceof Error) {
|
|
220
|
+
loader.prime(id, new ErrorWrapper(r));
|
|
221
|
+
return new ErrorWrapper(r);
|
|
222
|
+
}
|
|
223
|
+
else {
|
|
224
|
+
loader.prime(id, r);
|
|
225
|
+
return r;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
144
228
|
// this is the same implementation-wise (right now) as loadEnt. it's just clearer that it's not loaded via ID.
|
|
145
229
|
// used for load via email address etc
|
|
146
230
|
async function loadEntViaKey(viewer, key, options) {
|
|
@@ -150,29 +234,16 @@ async function loadEntViaKey(viewer, key, options) {
|
|
|
150
234
|
if (!row) {
|
|
151
235
|
return null;
|
|
152
236
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
const info = entFromCacheMaybe(viewer, row.id, options);
|
|
156
|
-
if (info.ent !== undefined) {
|
|
157
|
-
return info.ent;
|
|
158
|
-
}
|
|
159
|
-
return applyPrivacyPolicyForRowAndStoreInCache(viewer, options, row, info);
|
|
237
|
+
const r = await applyPrivacyPolicyForRowAndStoreInEntLoader(viewer, row, options);
|
|
238
|
+
return r instanceof ErrorWrapper ? null : r;
|
|
160
239
|
}
|
|
161
240
|
exports.loadEntViaKey = loadEntViaKey;
|
|
162
241
|
async function loadEntX(viewer, id, options) {
|
|
163
|
-
const
|
|
164
|
-
if (
|
|
165
|
-
throw
|
|
242
|
+
const r = await getEntLoader(viewer, options).load(id);
|
|
243
|
+
if (r instanceof ErrorWrapper) {
|
|
244
|
+
throw r.error;
|
|
166
245
|
}
|
|
167
|
-
|
|
168
|
-
return info.ent;
|
|
169
|
-
}
|
|
170
|
-
const row = await options.loaderFactory.createLoader(viewer.context).load(id);
|
|
171
|
-
if (!row) {
|
|
172
|
-
// todo make this better
|
|
173
|
-
throw new Error(`${options.loaderFactory.name}: couldn't find row for value ${id}`);
|
|
174
|
-
}
|
|
175
|
-
return applyPrivacyPolicyForRowAndStoreInCacheX(viewer, options, row, info);
|
|
246
|
+
return r;
|
|
176
247
|
}
|
|
177
248
|
exports.loadEntX = loadEntX;
|
|
178
249
|
async function loadEntXViaKey(viewer, key, options) {
|
|
@@ -183,14 +254,14 @@ async function loadEntXViaKey(viewer, key, options) {
|
|
|
183
254
|
// todo make this better
|
|
184
255
|
throw new Error(`${options.loaderFactory.name}: couldn't find row for value ${key}`);
|
|
185
256
|
}
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
if (
|
|
191
|
-
|
|
257
|
+
// this should have primed the other cache so there isn't a database query
|
|
258
|
+
// TODO every row.id needs to be audited...
|
|
259
|
+
// https://github.com/lolopinto/ent/issues/1064
|
|
260
|
+
const r = await getEntLoader(viewer, options).load(row.id);
|
|
261
|
+
if (r instanceof ErrorWrapper) {
|
|
262
|
+
throw r.error;
|
|
192
263
|
}
|
|
193
|
-
return
|
|
264
|
+
return r;
|
|
194
265
|
}
|
|
195
266
|
exports.loadEntXViaKey = loadEntXViaKey;
|
|
196
267
|
/**
|
|
@@ -203,6 +274,9 @@ async function loadEntFromClause(viewer, options, clause) {
|
|
|
203
274
|
context: viewer.context,
|
|
204
275
|
};
|
|
205
276
|
const row = await loadRow(rowOptions);
|
|
277
|
+
if (row === null) {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
206
280
|
return applyPrivacyPolicyForRow(viewer, options, row);
|
|
207
281
|
}
|
|
208
282
|
exports.loadEntFromClause = loadEntFromClause;
|
|
@@ -228,62 +302,15 @@ async function loadEnts(viewer, options, ...ids) {
|
|
|
228
302
|
}
|
|
229
303
|
// result
|
|
230
304
|
let m = new Map();
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
const key = getEntKey(viewer, id, options);
|
|
236
|
-
const ent = cache.get(key);
|
|
237
|
-
if (ent !== undefined) {
|
|
238
|
-
(0, logger_1.log)("cache", {
|
|
239
|
-
"ent-cache-hit": key,
|
|
240
|
-
});
|
|
241
|
-
if (ent === null) {
|
|
242
|
-
// TODO this should return null if not loadable...
|
|
243
|
-
// https://github.com/lolopinto/ent/issues/1070
|
|
244
|
-
continue;
|
|
245
|
-
}
|
|
246
|
-
// @ts-ignore
|
|
247
|
-
m.set(id, ent);
|
|
248
|
-
}
|
|
249
|
-
else {
|
|
250
|
-
toFetch.push(id);
|
|
251
|
-
}
|
|
305
|
+
const ret = await getEntLoader(viewer, options).loadMany(ids);
|
|
306
|
+
for (const r of ret) {
|
|
307
|
+
if (r instanceof Error) {
|
|
308
|
+
throw r;
|
|
252
309
|
}
|
|
253
|
-
|
|
254
|
-
else {
|
|
255
|
-
toFetch = ids;
|
|
256
|
-
}
|
|
257
|
-
// all in ent cache!
|
|
258
|
-
if (!toFetch.length) {
|
|
259
|
-
return m;
|
|
260
|
-
}
|
|
261
|
-
const l = options.loaderFactory.createLoader(viewer.context);
|
|
262
|
-
const rows = await l.loadMany(toFetch);
|
|
263
|
-
let rows2 = [];
|
|
264
|
-
for (const row of rows) {
|
|
265
|
-
if (!row) {
|
|
310
|
+
if (r instanceof ErrorWrapper) {
|
|
266
311
|
continue;
|
|
267
312
|
}
|
|
268
|
-
|
|
269
|
-
throw row;
|
|
270
|
-
}
|
|
271
|
-
rows2.push(row);
|
|
272
|
-
}
|
|
273
|
-
const m2 = await applyPrivacyPolicyForRows(viewer, rows2, options);
|
|
274
|
-
for (const row of rows2) {
|
|
275
|
-
const id = row[options.loaderFactory.options?.key || "id"];
|
|
276
|
-
const ent = m2.get(id);
|
|
277
|
-
if (cache) {
|
|
278
|
-
// put back in cache...
|
|
279
|
-
// store null for rows that can't be seen
|
|
280
|
-
cache.set(getEntKey(viewer, id, options), ent ?? null);
|
|
281
|
-
}
|
|
282
|
-
if (ent !== undefined) {
|
|
283
|
-
// TODO this should return null if not loadable...?
|
|
284
|
-
// TODO https://github.com/lolopinto/ent/issues/1070
|
|
285
|
-
m.set(id, ent);
|
|
286
|
-
}
|
|
313
|
+
m.set(r.id, r);
|
|
287
314
|
}
|
|
288
315
|
return m;
|
|
289
316
|
}
|
|
@@ -320,23 +347,16 @@ exports.loadEntsFromClause = loadEntsFromClause;
|
|
|
320
347
|
async function loadCustomEnts(viewer, options, query) {
|
|
321
348
|
const rows = await loadCustomData(options, query, viewer.context);
|
|
322
349
|
const result = new Array(rows.length);
|
|
350
|
+
if (!rows.length) {
|
|
351
|
+
return [];
|
|
352
|
+
}
|
|
353
|
+
const entLoader = getEntLoader(viewer, options);
|
|
323
354
|
await Promise.all(rows.map(async (row, idx) => {
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
const info = entFromCacheMaybe(viewer, row.id, options);
|
|
327
|
-
if (info.ent !== undefined) {
|
|
328
|
-
if (info.ent === null) {
|
|
329
|
-
// we're done here
|
|
330
|
-
return;
|
|
331
|
-
}
|
|
332
|
-
// @ts-ignore
|
|
333
|
-
result[idx] = info.ent;
|
|
355
|
+
const r = await applyPrivacyPolicyForRowAndStoreInEntLoader(viewer, row, options, entLoader);
|
|
356
|
+
if (r instanceof ErrorWrapper) {
|
|
334
357
|
return;
|
|
335
358
|
}
|
|
336
|
-
|
|
337
|
-
if (privacyEnt) {
|
|
338
|
-
result[idx] = privacyEnt;
|
|
339
|
-
}
|
|
359
|
+
result[idx] = r;
|
|
340
360
|
}));
|
|
341
361
|
// filter ents that aren't visible because of privacy
|
|
342
362
|
return result.filter((r) => r !== undefined);
|
|
@@ -457,14 +477,11 @@ exports.loadDerivedEntX = loadDerivedEntX;
|
|
|
457
477
|
// everything calls into this two so should be fine
|
|
458
478
|
// TODO is there a smarter way to not instantiate two objects here?
|
|
459
479
|
async function applyPrivacyPolicyForEnt(viewer, ent, data, fieldPrivacyOptions) {
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
return doFieldPrivacy(viewer, ent, data, fieldPrivacyOptions);
|
|
464
|
-
}
|
|
465
|
-
return error;
|
|
480
|
+
const error = await (0, privacy_1.applyPrivacyPolicyImpl)(viewer, ent.getPrivacyPolicy(), ent);
|
|
481
|
+
if (error === null) {
|
|
482
|
+
return doFieldPrivacy(viewer, ent, data, fieldPrivacyOptions);
|
|
466
483
|
}
|
|
467
|
-
return
|
|
484
|
+
return error;
|
|
468
485
|
}
|
|
469
486
|
async function applyPrivacyPolicyForEntX(viewer, ent, data, options) {
|
|
470
487
|
const r = await applyPrivacyPolicyForEnt(viewer, ent, data, options);
|
|
@@ -1505,9 +1522,6 @@ async function applyPrivacyPolicyForRow(viewer, options, row) {
|
|
|
1505
1522
|
}
|
|
1506
1523
|
exports.applyPrivacyPolicyForRow = applyPrivacyPolicyForRow;
|
|
1507
1524
|
async function applyPrivacyPolicyForRowImpl(viewer, options, row) {
|
|
1508
|
-
if (!row) {
|
|
1509
|
-
return null;
|
|
1510
|
-
}
|
|
1511
1525
|
const ent = new options.ent(viewer, row);
|
|
1512
1526
|
return applyPrivacyPolicyForEnt(viewer, ent, row, options);
|
|
1513
1527
|
}
|
|
@@ -11,7 +11,7 @@ export declare class ObjectLoader<T> implements Loader<T, Data | null> {
|
|
|
11
11
|
private initPrime;
|
|
12
12
|
load(key: T): Promise<Data | null>;
|
|
13
13
|
clearAll(): void;
|
|
14
|
-
loadMany(keys: T[]): Promise<Data
|
|
14
|
+
loadMany(keys: T[]): Promise<Array<Data | null>>;
|
|
15
15
|
prime(data: Data): void;
|
|
16
16
|
primeAll(data: Data): void;
|
|
17
17
|
}
|
|
@@ -24,6 +24,6 @@ export declare class ObjectLoaderFactory<T> implements LoaderFactory<T, Data | n
|
|
|
24
24
|
private toPrime;
|
|
25
25
|
constructor(options: ObjectLoaderOptions);
|
|
26
26
|
createLoader(context?: Context): ObjectLoader<T>;
|
|
27
|
-
addToPrime(factory: ObjectLoaderFactory<T>):
|
|
27
|
+
addToPrime(factory: ObjectLoaderFactory<T>): this;
|
|
28
28
|
}
|
|
29
29
|
export {};
|
|
@@ -29,6 +29,47 @@ const clause = __importStar(require("../clause"));
|
|
|
29
29
|
const logger_1 = require("../logger");
|
|
30
30
|
const loader_1 = require("./loader");
|
|
31
31
|
const memoizee_1 = __importDefault(require("memoizee"));
|
|
32
|
+
async function loadRowsForLoader(options, ids, context) {
|
|
33
|
+
let col = options.key;
|
|
34
|
+
let cls = clause.In(col, ...ids);
|
|
35
|
+
if (options.clause) {
|
|
36
|
+
let optionClause;
|
|
37
|
+
if (typeof options.clause === "function") {
|
|
38
|
+
optionClause = options.clause();
|
|
39
|
+
}
|
|
40
|
+
else {
|
|
41
|
+
optionClause = options.clause;
|
|
42
|
+
}
|
|
43
|
+
if (optionClause) {
|
|
44
|
+
cls = clause.And(cls, optionClause);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
const rowOptions = {
|
|
48
|
+
...options,
|
|
49
|
+
clause: cls,
|
|
50
|
+
context,
|
|
51
|
+
};
|
|
52
|
+
let m = new Map();
|
|
53
|
+
let result = [];
|
|
54
|
+
for (let i = 0; i < ids.length; i++) {
|
|
55
|
+
result.push(null);
|
|
56
|
+
// store the index....
|
|
57
|
+
m.set(ids[i], i);
|
|
58
|
+
}
|
|
59
|
+
const rows = await (0, ent_1.loadRows)(rowOptions);
|
|
60
|
+
for (const row of rows) {
|
|
61
|
+
const id = row[col];
|
|
62
|
+
if (id === undefined) {
|
|
63
|
+
throw new Error(`need to query for column ${col} when using an object loader because the query may not be sorted and we need the id to maintain sort order`);
|
|
64
|
+
}
|
|
65
|
+
const idx = m.get(id);
|
|
66
|
+
if (idx === undefined) {
|
|
67
|
+
throw new Error(`malformed query. got ${id} back but didn't query for it`);
|
|
68
|
+
}
|
|
69
|
+
result[idx] = row;
|
|
70
|
+
}
|
|
71
|
+
return result;
|
|
72
|
+
}
|
|
32
73
|
// optional clause...
|
|
33
74
|
// so ObjectLoaderFactory and createDataLoader need to take a new optional field which is a clause that's always added here
|
|
34
75
|
// and we need a disableTransform which skips loader completely and uses loadRow...
|
|
@@ -42,45 +83,8 @@ function createDataLoader(options) {
|
|
|
42
83
|
if (!ids.length) {
|
|
43
84
|
return [];
|
|
44
85
|
}
|
|
45
|
-
let col = options.key;
|
|
46
|
-
let cls = clause.In(col, ...ids);
|
|
47
|
-
if (options.clause) {
|
|
48
|
-
let optionClause;
|
|
49
|
-
if (typeof options.clause === "function") {
|
|
50
|
-
optionClause = options.clause();
|
|
51
|
-
}
|
|
52
|
-
else {
|
|
53
|
-
optionClause = options.clause;
|
|
54
|
-
}
|
|
55
|
-
if (optionClause) {
|
|
56
|
-
cls = clause.And(cls, optionClause);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
const rowOptions = {
|
|
60
|
-
...options,
|
|
61
|
-
clause: cls,
|
|
62
|
-
};
|
|
63
|
-
let m = new Map();
|
|
64
|
-
let result = [];
|
|
65
|
-
for (let i = 0; i < ids.length; i++) {
|
|
66
|
-
result.push(null);
|
|
67
|
-
// store the index....
|
|
68
|
-
m.set(ids[i], i);
|
|
69
|
-
}
|
|
70
86
|
// context not needed because we're creating a loader which has its own cache which is being used here
|
|
71
|
-
|
|
72
|
-
for (const row of rows) {
|
|
73
|
-
const id = row[col];
|
|
74
|
-
if (id === undefined) {
|
|
75
|
-
throw new Error(`need to query for column ${col} when using an object loader because the query may not be sorted and we need the id to maintain sort order`);
|
|
76
|
-
}
|
|
77
|
-
const idx = m.get(id);
|
|
78
|
-
if (idx === undefined) {
|
|
79
|
-
throw new Error(`malformed query. got ${id} back but didn't query for it`);
|
|
80
|
-
}
|
|
81
|
-
result[idx] = row;
|
|
82
|
-
}
|
|
83
|
-
return result;
|
|
87
|
+
return loadRowsForLoader(options, ids);
|
|
84
88
|
}, loaderOptions);
|
|
85
89
|
}
|
|
86
90
|
class ObjectLoader {
|
|
@@ -147,7 +151,7 @@ class ObjectLoader {
|
|
|
147
151
|
clause: cls,
|
|
148
152
|
context: this.context,
|
|
149
153
|
};
|
|
150
|
-
return
|
|
154
|
+
return (0, ent_1.loadRow)(rowOptions);
|
|
151
155
|
}
|
|
152
156
|
clearAll() {
|
|
153
157
|
this.loader && this.loader.clearAll();
|
|
@@ -156,25 +160,7 @@ class ObjectLoader {
|
|
|
156
160
|
if (this.loader) {
|
|
157
161
|
return await this.loader.loadMany(keys);
|
|
158
162
|
}
|
|
159
|
-
|
|
160
|
-
if (this.options.clause) {
|
|
161
|
-
let optionClause;
|
|
162
|
-
if (typeof this.options.clause === "function") {
|
|
163
|
-
optionClause = this.options.clause();
|
|
164
|
-
}
|
|
165
|
-
else {
|
|
166
|
-
optionClause = this.options.clause;
|
|
167
|
-
}
|
|
168
|
-
if (optionClause) {
|
|
169
|
-
cls = clause.And(cls, optionClause);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
const rowOptions = {
|
|
173
|
-
...this.options,
|
|
174
|
-
clause: cls,
|
|
175
|
-
context: this.context,
|
|
176
|
-
};
|
|
177
|
-
return await (0, ent_1.loadRows)(rowOptions);
|
|
163
|
+
return loadRowsForLoader(this.options, keys, this.context);
|
|
178
164
|
}
|
|
179
165
|
prime(data) {
|
|
180
166
|
// we have this data from somewhere else, prime it in the c
|
|
@@ -225,6 +211,7 @@ class ObjectLoaderFactory {
|
|
|
225
211
|
// because there's usually self references here
|
|
226
212
|
addToPrime(factory) {
|
|
227
213
|
this.toPrime.push(factory);
|
|
214
|
+
return this;
|
|
228
215
|
}
|
|
229
216
|
}
|
|
230
217
|
exports.ObjectLoaderFactory = ObjectLoaderFactory;
|
package/package.json
CHANGED
package/schema/field.d.ts
CHANGED
|
@@ -131,8 +131,6 @@ export interface EnumOptions extends FieldOptions {
|
|
|
131
131
|
graphQLType?: string;
|
|
132
132
|
createEnumType?: boolean;
|
|
133
133
|
}
|
|
134
|
-
export interface StringEnumOptions extends EnumOptions {
|
|
135
|
-
}
|
|
136
134
|
/**
|
|
137
135
|
* @deprecated Use StringEnumField
|
|
138
136
|
*/
|
|
@@ -147,6 +145,11 @@ export declare class EnumField extends BaseField implements Field {
|
|
|
147
145
|
}
|
|
148
146
|
export declare class StringEnumField extends EnumField {
|
|
149
147
|
}
|
|
148
|
+
export interface PolymorphicStringEnumOptions extends EnumOptions {
|
|
149
|
+
parentFieldToValidate: string;
|
|
150
|
+
}
|
|
151
|
+
export interface StringEnumOptions extends EnumOptions {
|
|
152
|
+
}
|
|
150
153
|
export declare function EnumType(options: StringEnumOptions): EnumField;
|
|
151
154
|
declare type IntEnumMap = {
|
|
152
155
|
[key: string]: number;
|
|
@@ -165,11 +168,15 @@ export declare class IntegerEnumField extends BaseField implements Field {
|
|
|
165
168
|
format(val: any): any;
|
|
166
169
|
}
|
|
167
170
|
export declare function IntegerEnumType(options: IntegerEnumOptions): IntegerEnumField;
|
|
171
|
+
interface ListOptions extends FieldOptions {
|
|
172
|
+
disableJSONStringify?: boolean;
|
|
173
|
+
}
|
|
168
174
|
export declare class ListField extends BaseField {
|
|
169
175
|
private field;
|
|
176
|
+
private options?;
|
|
170
177
|
type: Type;
|
|
171
178
|
private validators;
|
|
172
|
-
constructor(field: Field, options?:
|
|
179
|
+
constructor(field: Field, options?: ListOptions | undefined);
|
|
173
180
|
__getElemField(): Field;
|
|
174
181
|
validate(validator: (val: any[]) => boolean): this;
|
|
175
182
|
valid(val: any): Promise<boolean>;
|
package/schema/field.js
CHANGED
|
@@ -22,10 +22,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
22
22
|
exports.UUIDListType = exports.IntegerEnumListType = exports.EnumListType = exports.DateListType = exports.TimetzListType = exports.TimeListType = exports.TimestamptzListType = exports.TimestampListType = exports.BooleanListType = exports.BigIntegerListType = exports.FloatListType = exports.IntegerListType = exports.IntListType = exports.StringListType = exports.ListField = exports.IntegerEnumType = exports.IntegerEnumField = exports.EnumType = exports.StringEnumField = exports.EnumField = exports.DateType = exports.DateField = exports.TimetzType = exports.TimeType = exports.TimeField = exports.leftPad = exports.TimestamptzType = exports.TimestampType = exports.TimestampField = exports.StringType = exports.StringField = exports.BooleanType = exports.BooleanField = exports.FloatType = exports.FloatField = exports.BigIntegerType = exports.BigIntegerField = exports.IntegerType = exports.IntegerField = exports.UUIDType = exports.UUIDField = exports.BaseField = void 0;
|
|
23
23
|
const luxon_1 = require("luxon");
|
|
24
24
|
const snake_case_1 = require("snake-case");
|
|
25
|
-
const db_1 = __importStar(require("../core/db"));
|
|
26
|
-
const schema_1 = require("./schema");
|
|
27
25
|
const util_1 = require("util");
|
|
28
26
|
const uuid_1 = require("uuid");
|
|
27
|
+
const base_1 = require("../core/base");
|
|
28
|
+
const db_1 = __importStar(require("../core/db"));
|
|
29
|
+
const schema_1 = require("./schema");
|
|
29
30
|
class BaseField {
|
|
30
31
|
logValue(val) {
|
|
31
32
|
if (this.sensitive) {
|
|
@@ -68,21 +69,22 @@ class UUIDField extends BaseField {
|
|
|
68
69
|
if (typeof polymorphic === "object" && polymorphic.types) {
|
|
69
70
|
// an enum with types validated here
|
|
70
71
|
return {
|
|
71
|
-
[name]:
|
|
72
|
+
[name]: PolymorphicStringEnumType({
|
|
72
73
|
values: polymorphic.types,
|
|
73
74
|
hideFromGraphQL: true,
|
|
74
75
|
derivedWhenEmbedded: true,
|
|
75
76
|
nullable: this.options?.nullable,
|
|
77
|
+
parentFieldToValidate: fieldName,
|
|
76
78
|
}),
|
|
77
79
|
};
|
|
78
80
|
}
|
|
79
81
|
else {
|
|
80
|
-
// just a string field...
|
|
81
82
|
return {
|
|
82
|
-
[name]:
|
|
83
|
+
[name]: PolymorphicStringType({
|
|
83
84
|
hideFromGraphQL: true,
|
|
84
85
|
derivedWhenEmbedded: true,
|
|
85
86
|
nullable: this.options?.nullable,
|
|
87
|
+
parentFieldToValidate: fieldName,
|
|
86
88
|
}),
|
|
87
89
|
};
|
|
88
90
|
}
|
|
@@ -311,6 +313,33 @@ class StringField extends BaseField {
|
|
|
311
313
|
}
|
|
312
314
|
}
|
|
313
315
|
exports.StringField = StringField;
|
|
316
|
+
function validatePolymorphicTypeWithFullData(val, b, field) {
|
|
317
|
+
const input = b.getInput();
|
|
318
|
+
const inputKey = b.orchestrator.__getOptions().fieldInfo[field].inputKey;
|
|
319
|
+
const v = input[inputKey];
|
|
320
|
+
if (val === null) {
|
|
321
|
+
// if this is being set to null, ok if v is also null
|
|
322
|
+
return v === null;
|
|
323
|
+
}
|
|
324
|
+
// if this is not being set, ok if v is not being set
|
|
325
|
+
if (val === undefined && b.operation === base_1.WriteOperation.Insert) {
|
|
326
|
+
return v === undefined;
|
|
327
|
+
}
|
|
328
|
+
return true;
|
|
329
|
+
}
|
|
330
|
+
class PolymorphicStringField extends StringField {
|
|
331
|
+
constructor(opts) {
|
|
332
|
+
super(opts);
|
|
333
|
+
this.opts = opts;
|
|
334
|
+
}
|
|
335
|
+
validateWithFullData(val, b) {
|
|
336
|
+
return validatePolymorphicTypeWithFullData(val, b, this.opts.parentFieldToValidate);
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function PolymorphicStringType(opts) {
|
|
340
|
+
let result = new PolymorphicStringField(opts);
|
|
341
|
+
return Object.assign(result, opts);
|
|
342
|
+
}
|
|
314
343
|
function StringType(options) {
|
|
315
344
|
let result = new StringField(options);
|
|
316
345
|
const options2 = { ...options };
|
|
@@ -556,6 +585,19 @@ exports.EnumField = EnumField;
|
|
|
556
585
|
class StringEnumField extends EnumField {
|
|
557
586
|
}
|
|
558
587
|
exports.StringEnumField = StringEnumField;
|
|
588
|
+
class PolymorphicStringEnumField extends StringEnumField {
|
|
589
|
+
constructor(opts) {
|
|
590
|
+
super(opts);
|
|
591
|
+
this.opts = opts;
|
|
592
|
+
}
|
|
593
|
+
validateWithFullData(val, b) {
|
|
594
|
+
return validatePolymorphicTypeWithFullData(val, b, this.opts.parentFieldToValidate);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
function PolymorphicStringEnumType(options) {
|
|
598
|
+
let result = new PolymorphicStringEnumField(options);
|
|
599
|
+
return Object.assign(result, options);
|
|
600
|
+
}
|
|
559
601
|
function EnumType(options) {
|
|
560
602
|
let result = new StringEnumField(options);
|
|
561
603
|
return Object.assign(result, options);
|
|
@@ -608,6 +650,7 @@ class ListField extends BaseField {
|
|
|
608
650
|
constructor(field, options) {
|
|
609
651
|
super();
|
|
610
652
|
this.field = field;
|
|
653
|
+
this.options = options;
|
|
611
654
|
this.validators = [];
|
|
612
655
|
if (field.type.dbType === schema_1.DBType.List) {
|
|
613
656
|
throw new Error(`nested lists not currently supported`);
|
|
@@ -648,11 +691,12 @@ class ListField extends BaseField {
|
|
|
648
691
|
return result;
|
|
649
692
|
}
|
|
650
693
|
postgresVal(val, jsonType) {
|
|
651
|
-
if (!jsonType) {
|
|
694
|
+
if (!jsonType && val === "") {
|
|
652
695
|
// support empty strings in list
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
696
|
+
val = '"' + val + '"';
|
|
697
|
+
return val;
|
|
698
|
+
}
|
|
699
|
+
if (this.options?.disableJSONStringify) {
|
|
656
700
|
return val;
|
|
657
701
|
}
|
|
658
702
|
return JSON.stringify(val);
|
|
@@ -789,6 +833,9 @@ function IntegerEnumListType(options) {
|
|
|
789
833
|
}
|
|
790
834
|
exports.IntegerEnumListType = IntegerEnumListType;
|
|
791
835
|
function UUIDListType(options) {
|
|
792
|
-
return new ListField(UUIDType(options),
|
|
836
|
+
return new ListField(UUIDType(options), {
|
|
837
|
+
...options,
|
|
838
|
+
disableJSONStringify: true,
|
|
839
|
+
});
|
|
793
840
|
}
|
|
794
841
|
exports.UUIDListType = UUIDListType;
|
package/schema/schema.d.ts
CHANGED
|
@@ -217,6 +217,7 @@ export interface PolymorphicOptions {
|
|
|
217
217
|
export interface Field extends FieldOptions {
|
|
218
218
|
type: Type;
|
|
219
219
|
valid?(val: any): Promise<boolean> | boolean;
|
|
220
|
+
validateWithFullData?(val: any, builder: Builder<any>): boolean | Promise<boolean>;
|
|
220
221
|
format?(val: any, nested?: boolean): any;
|
|
221
222
|
logValue(val: any): any;
|
|
222
223
|
}
|
package/schema/struct_field.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.StructTypeAsList = exports.StructListType = exports.StructType = exports.StructField = void 0;
|
|
4
|
+
const camel_case_1 = require("camel-case");
|
|
4
5
|
const field_1 = require("./field");
|
|
5
6
|
const schema_1 = require("./schema");
|
|
6
|
-
const camel_case_1 = require("camel-case");
|
|
7
7
|
class StructField extends field_1.BaseField {
|
|
8
8
|
constructor(options) {
|
|
9
9
|
super();
|
|
@@ -26,10 +26,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
26
26
|
const glob_1 = __importDefault(require("glob"));
|
|
27
27
|
const json5_1 = __importDefault(require("json5"));
|
|
28
28
|
const minimist_1 = __importDefault(require("minimist"));
|
|
29
|
-
const graphql_1 = require("../graphql/graphql");
|
|
30
|
-
const readline = __importStar(require("readline"));
|
|
31
29
|
const path = __importStar(require("path"));
|
|
32
30
|
const fs = __importStar(require("fs"));
|
|
31
|
+
const graphql_1 = require("../graphql/graphql");
|
|
32
|
+
const readline = __importStar(require("readline"));
|
|
33
33
|
const imports_1 = require("../imports");
|
|
34
34
|
const process_1 = require("process");
|
|
35
35
|
// need to use the GQLCapture from the package so that when we call GQLCapture.enable()
|
package/scripts/migrate_v0.1.js
CHANGED
|
@@ -3,12 +3,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const minimist_1 = __importDefault(require("minimist"));
|
|
6
7
|
const transform_1 = require("../tsc/transform");
|
|
7
8
|
const transform_schema_1 = require("../tsc/transform_schema");
|
|
8
9
|
const transform_ent_1 = require("../tsc/transform_ent");
|
|
9
10
|
const move_generated_1 = require("../tsc/move_generated");
|
|
10
11
|
const transform_action_1 = require("../tsc/transform_action");
|
|
11
|
-
const minimist_1 = __importDefault(require("minimist"));
|
|
12
12
|
const ast_1 = require("../tsc/ast");
|
|
13
13
|
// todo-sqlite
|
|
14
14
|
// ts-node-script --swc --project ./tsconfig.json -r tsconfig-paths/register ../../ts/src/scripts/migrate_v0.1.ts --transform_schema --old_base_class BaseEntTodoSchema --new_schema_class TodoEntSchema --transform_path src/schema/patterns/base
|
package/testutils/builder.js
CHANGED
|
@@ -137,7 +137,8 @@ function getFieldInfo(value) {
|
|
|
137
137
|
for (const [k, f] of fields) {
|
|
138
138
|
ret[k] = {
|
|
139
139
|
dbCol: (0, schema_2.getStorageKey)(f, k),
|
|
140
|
-
|
|
140
|
+
// in tests (anything using SimpleBuilder), make it be the same as the fieldName
|
|
141
|
+
inputKey: k,
|
|
141
142
|
};
|
|
142
143
|
}
|
|
143
144
|
return ret;
|