@snowtop/ent 0.1.0-alpha74 → 0.1.0-alpha78

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.
@@ -53,7 +53,7 @@ export interface Action<TEnt extends Ent<TViewer>, TBuilder extends Builder<TEnt
53
53
  getObservers?(): Observer<TEnt, TBuilder, TViewer, TInput, TExistingEnt>[];
54
54
  getValidators?(): Validator<TEnt, TBuilder, TViewer, TInput, TExistingEnt>[];
55
55
  getInput(): TInput;
56
- transformWrite?: (stmt: UpdateOperation<TEnt, TViewer>) => Promise<TransformedUpdateOperation<TEnt>> | TransformedUpdateOperation<TEnt> | null;
56
+ transformWrite?: (stmt: UpdateOperation<TEnt, TViewer>) => Promise<TransformedUpdateOperation<TEnt, TViewer>> | TransformedUpdateOperation<TEnt, TViewer> | null;
57
57
  valid(): Promise<boolean>;
58
58
  validX(): Promise<void>;
59
59
  viewerForEntLoad?(data: Data, context?: Context<TViewer>): TViewer | Promise<TViewer>;
@@ -108,6 +108,7 @@ export declare class EntChangeset<T extends Ent> implements Changeset {
108
108
  private options?;
109
109
  private _executor;
110
110
  constructor(viewer: Viewer, placeholderID: ID, ent: EntConstructor<T>, operations: DataOperation[], dependencies?: Map<ID, Builder<Ent<Viewer<Ent<any> | null, ID | null>>, Viewer<Ent<any> | null, ID | null>, Ent<Viewer<Ent<any> | null, ID | null>> | null>> | undefined, changesets?: Changeset[] | undefined, options?: OrchestratorOptions<T, Data, Viewer<Ent<any> | null, ID | null>, MaybeNull<T>> | undefined);
111
+ static changesetFrom(builder: Builder<any, any, any>, ops: DataOperation[]): EntChangeset<any>;
111
112
  executor(): Executor;
112
113
  }
113
114
  export {};
@@ -487,6 +487,10 @@ class Orchestrator {
487
487
  // this.defaultFieldsByFieldName[k] = val;
488
488
  }
489
489
  }
490
+ if (transformed.changeset) {
491
+ const ct = await transformed.changeset();
492
+ this.changesets.push(ct);
493
+ }
490
494
  this.actualOperation = this.getWriteOpForSQLStamentOp(transformed.op);
491
495
  if (transformed.existingEnt) {
492
496
  // @ts-ignore
@@ -719,6 +723,9 @@ class Orchestrator {
719
723
  }
720
724
  }
721
725
  exports.Orchestrator = Orchestrator;
726
+ function randomNum() {
727
+ return Math.random().toString(10).substring(2);
728
+ }
722
729
  class EntChangeset {
723
730
  constructor(viewer, placeholderID, ent, operations, dependencies, changesets, options) {
724
731
  this.viewer = viewer;
@@ -729,6 +736,9 @@ class EntChangeset {
729
736
  this.changesets = changesets;
730
737
  this.options = options;
731
738
  }
739
+ static changesetFrom(builder, ops) {
740
+ return new EntChangeset(builder.viewer, `$ent.idPlaceholderID$ ${randomNum()}-${builder.ent.name}`, builder.ent, ops);
741
+ }
732
742
  executor() {
733
743
  if (this._executor) {
734
744
  return this._executor;
package/core/base.d.ts CHANGED
@@ -5,16 +5,23 @@ 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<(V | null)[]>;
10
+ }
8
11
  export interface LoaderFactory<T, V> {
9
12
  name: string;
10
13
  createLoader(context?: Context): Loader<T, V>;
11
14
  }
15
+ interface LoaderFactoryWithLoaderMany<T, V> extends LoaderFactory<T, V> {
16
+ createLoader(context?: Context): LoaderWithLoadMany<T, V>;
17
+ }
12
18
  export interface ConfigurableLoaderFactory<T, V> extends LoaderFactory<T, V> {
13
19
  createConfigurableLoader(options: EdgeQueryableDataOptions, context?: Context): Loader<T, V>;
14
20
  }
15
21
  export declare type EdgeQueryableDataOptions = Partial<Pick<QueryableDataOptions, "limit" | "orderby" | "clause">>;
16
22
  export interface PrimableLoader<T, V> extends Loader<T, V> {
17
23
  prime(d: Data): void;
24
+ primeAll?(d: Data): void;
18
25
  }
19
26
  interface cache {
20
27
  getLoader<T, V>(name: string, create: () => Loader<T, V>): Loader<T, V>;
@@ -23,6 +30,7 @@ interface cache {
23
30
  primeCache(options: queryOptions, rows: Data[]): void;
24
31
  primeCache(options: queryOptions, rows: Data): void;
25
32
  clearCache(): void;
33
+ getEntCache(): Map<string, Ent | Error | null>;
26
34
  }
27
35
  interface queryOptions {
28
36
  fields: string[];
@@ -89,15 +97,15 @@ interface LoadableEntOptions<TEnt extends Ent, TViewer extends Viewer = Viewer>
89
97
  loaderFactory: LoaderFactoryWithOptions;
90
98
  ent: EntConstructor<TEnt, TViewer>;
91
99
  }
92
- interface LoaderFactoryWithOptions extends LoaderFactory<any, Data | null> {
100
+ interface LoaderFactoryWithOptions extends LoaderFactoryWithLoaderMany<any, Data | null> {
93
101
  options?: SelectDataOptions;
94
102
  }
95
103
  export interface LoadEntOptions<TEnt extends Ent, TViewer extends Viewer = Viewer> extends LoadableEntOptions<TEnt, TViewer>, SelectBaseDataOptions {
96
104
  fieldPrivacy?: Map<string, PrivacyPolicy>;
97
105
  }
98
106
  export interface SelectCustomDataOptions extends SelectBaseDataOptions {
99
- clause?: clause.Clause;
100
- loaderFactory?: LoaderFactoryWithOptions;
107
+ loaderFactory: LoaderFactoryWithOptions;
108
+ prime?: boolean;
101
109
  }
102
110
  export interface LoadCustomEntOptions<TEnt extends Ent, TViewer extends Viewer = Viewer> extends SelectCustomDataOptions {
103
111
  ent: EntConstructor<TEnt, TViewer>;
package/core/clause.d.ts CHANGED
@@ -22,6 +22,18 @@ declare class simpleClause implements Clause {
22
22
  logValues(): any[];
23
23
  instanceKey(): string;
24
24
  }
25
+ export declare class inClause implements Clause {
26
+ private col;
27
+ private value;
28
+ private type;
29
+ static getPostgresInClauseValuesThreshold(): number;
30
+ constructor(col: string, value: any[], type?: string);
31
+ clause(idx: number): string;
32
+ columns(): string[];
33
+ values(): any[];
34
+ logValues(): any[];
35
+ instanceKey(): string;
36
+ }
25
37
  declare class compositeClause implements Clause {
26
38
  private clauses;
27
39
  private sep;
@@ -86,6 +98,7 @@ export declare function And(...args: Clause[]): compositeClause;
86
98
  export declare function AndOptional(...args: (Clause | undefined)[]): Clause;
87
99
  export declare function Or(...args: Clause[]): compositeClause;
88
100
  export declare function In(col: string, ...values: any): Clause;
101
+ export declare function In(col: string, values: any[], type?: string): Clause;
89
102
  interface TsQuery {
90
103
  language: "english" | "french" | "german" | "simple";
91
104
  value: string;
package/core/clause.js CHANGED
@@ -19,7 +19,7 @@ var __importStar = (this && this.__importStar) || function (mod) {
19
19
  return result;
20
20
  };
21
21
  Object.defineProperty(exports, "__esModule", { value: true });
22
- exports.JSONPathValuePredicate = exports.JSONObjectFieldKeyAsText = exports.JSONObjectFieldKeyASJSON = exports.sensitiveValue = exports.TsVectorWebsearchToTsQuery = exports.TsVectorPhraseToTsQuery = exports.TsVectorPlainToTsQuery = exports.TsVectorColTsQuery = exports.WebsearchToTsQuery = exports.PhraseToTsQuery = exports.PlainToTsQuery = exports.TsQuery = exports.In = exports.Or = exports.AndOptional = exports.And = exports.LessEq = exports.GreaterEq = exports.Less = exports.Greater = exports.NotEq = exports.Eq = exports.ArrayNotEq = exports.ArrayEq = exports.PostgresArrayNotOverlaps = exports.PostgresArrayOverlaps = exports.PostgresArrayNotContains = exports.PostgresArrayNotContainsValue = exports.PostgresArrayContains = exports.PostgresArrayContainsValue = void 0;
22
+ exports.JSONPathValuePredicate = exports.JSONObjectFieldKeyAsText = exports.JSONObjectFieldKeyASJSON = exports.sensitiveValue = exports.TsVectorWebsearchToTsQuery = exports.TsVectorPhraseToTsQuery = exports.TsVectorPlainToTsQuery = exports.TsVectorColTsQuery = exports.WebsearchToTsQuery = exports.PhraseToTsQuery = exports.PlainToTsQuery = exports.TsQuery = exports.In = exports.Or = exports.AndOptional = exports.And = exports.LessEq = exports.GreaterEq = exports.Less = exports.Greater = exports.NotEq = exports.Eq = exports.ArrayNotEq = exports.ArrayEq = exports.PostgresArrayNotOverlaps = exports.PostgresArrayOverlaps = exports.PostgresArrayNotContains = exports.PostgresArrayNotContainsValue = exports.PostgresArrayContains = exports.PostgresArrayContainsValue = exports.inClause = void 0;
23
23
  const db_1 = __importStar(require("./db"));
24
24
  function isSensitive(val) {
25
25
  return (val !== null &&
@@ -213,17 +213,37 @@ class postgresArrayOperatorList extends postgresArrayOperator {
213
213
  }
214
214
  }
215
215
  class inClause {
216
- constructor(col, value) {
216
+ constructor(col, value, type = "uuid") {
217
217
  this.col = col;
218
218
  this.value = value;
219
+ this.type = type;
220
+ }
221
+ static getPostgresInClauseValuesThreshold() {
222
+ return 70;
219
223
  }
220
224
  clause(idx) {
221
- const dialect = db_1.default.getDialect();
225
+ // do a simple = when only one item
226
+ if (this.value.length === 1) {
227
+ return new simpleClause(this.col, this.value[0], "=").clause(idx);
228
+ }
229
+ const postgres = db_1.default.getDialect() === db_1.Dialect.Postgres;
230
+ const postgresValuesList = postgres &&
231
+ this.value.length >= inClause.getPostgresInClauseValuesThreshold();
222
232
  let indices;
223
- if (dialect === db_1.Dialect.Postgres) {
233
+ if (postgres) {
224
234
  indices = [];
225
235
  for (let i = 0; i < this.value.length; i++) {
226
- indices.push(`$${idx}`);
236
+ if (postgresValuesList) {
237
+ if (i === 0) {
238
+ indices.push(`($${idx}::${this.type})`);
239
+ }
240
+ else {
241
+ indices.push(`($${idx})`);
242
+ }
243
+ }
244
+ else {
245
+ indices.push(`$${idx}`);
246
+ }
227
247
  idx++;
228
248
  }
229
249
  }
@@ -231,7 +251,11 @@ class inClause {
231
251
  indices = new Array(this.value.length);
232
252
  indices.fill("?", 0);
233
253
  }
234
- const inValue = indices.join(", ");
254
+ let inValue = indices.join(", ");
255
+ // wrap in VALUES list for postgres...
256
+ if (postgresValuesList) {
257
+ inValue = `VALUES${inValue}`;
258
+ }
235
259
  return `${this.col} IN (${inValue})`;
236
260
  // TODO we need to return idx at end to query builder...
237
261
  // or anything that's doing a composite query so next clause knows where to start
@@ -243,25 +267,15 @@ class inClause {
243
267
  }
244
268
  values() {
245
269
  const result = [];
246
- for (const value of this.value) {
247
- if (isSensitive(value)) {
248
- result.push(value.value());
249
- }
250
- else {
251
- result.push(value);
252
- }
270
+ for (let value of this.value) {
271
+ result.push(rawValue(value));
253
272
  }
254
273
  return result;
255
274
  }
256
275
  logValues() {
257
276
  const result = [];
258
- for (const value of this.value) {
259
- if (isSensitive(value)) {
260
- result.push(value.logValue());
261
- }
262
- else {
263
- result.push(value);
264
- }
277
+ for (let value of this.value) {
278
+ result.push(isSensitive(value) ? value.logValue() : value);
265
279
  }
266
280
  return result;
267
281
  }
@@ -269,6 +283,7 @@ class inClause {
269
283
  return `in:${this.col}:${this.values().join(",")}`;
270
284
  }
271
285
  }
286
+ exports.inClause = inClause;
272
287
  class compositeClause {
273
288
  constructor(clauses, sep) {
274
289
  this.clauses = clauses;
@@ -486,9 +501,15 @@ function Or(...args) {
486
501
  return new compositeClause(args, " OR ");
487
502
  }
488
503
  exports.Or = Or;
489
- // TODO this breaks if values.length ===1 and array. todo fix
490
- function In(col, ...values) {
491
- return new inClause(col, values);
504
+ function In(...args) {
505
+ if (args.length < 2) {
506
+ throw new Error(`invalid args passed to In`);
507
+ }
508
+ // 2nd overload
509
+ if (Array.isArray(args[1])) {
510
+ return new inClause(args[0], args[1], args[2]);
511
+ }
512
+ return new inClause(args[0], args.slice(1));
492
513
  }
493
514
  exports.In = In;
494
515
  // if string defaults to english
package/core/context.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /// <reference types="node" />
2
- import { Viewer, Data, Loader } from "./base";
2
+ import { Viewer, Data, Loader, Ent } from "./base";
3
3
  import { IncomingMessage, ServerResponse } from "http";
4
4
  import * as clause from "./clause";
5
5
  import { Context } from "./base";
@@ -14,12 +14,14 @@ export declare class ContextCache {
14
14
  getLoader<T, V>(name: string, create: () => Loader<T, V>): Loader<T, V>;
15
15
  private itemMap;
16
16
  private listMap;
17
+ private entCache;
17
18
  private getkey;
18
19
  getCachedRows(options: queryOptions): Data[] | null;
19
20
  getCachedRow(options: queryOptions): Data | null;
20
21
  primeCache(options: queryOptions, rows: Data[]): void;
21
22
  primeCache(options: queryOptions, rows: Data): void;
22
23
  clearCache(): void;
24
+ getEntCache(): Map<string, Error | Ent<any> | null>;
23
25
  }
24
26
  interface queryOptions {
25
27
  fields: string[];
package/core/context.js CHANGED
@@ -8,6 +8,7 @@ class ContextCache {
8
8
  // we have a per-table map to make it easier to purge and have less things to compare with
9
9
  this.itemMap = new Map();
10
10
  this.listMap = new Map();
11
+ this.entCache = new Map();
11
12
  }
12
13
  getLoader(name, create) {
13
14
  let l = this.loaders.get(name);
@@ -82,6 +83,10 @@ class ContextCache {
82
83
  this.loaders.clear();
83
84
  this.itemMap.clear();
84
85
  this.listMap.clear();
86
+ this.entCache.clear();
87
+ }
88
+ getEntCache() {
89
+ return this.entCache;
85
90
  }
86
91
  }
87
92
  exports.ContextCache = ContextCache;
package/core/db.d.ts CHANGED
@@ -28,7 +28,7 @@ export default class DB {
28
28
  getConnection(): Connection;
29
29
  getPool(): Queryer;
30
30
  getNewClient(): Promise<Client>;
31
- getSQLiteClient(): Promise<Sqlite>;
31
+ getSQLiteClient(): Sqlite;
32
32
  endPool(): Promise<void>;
33
33
  static getInstance(): DB;
34
34
  static getDialect(): Dialect;
package/core/db.js CHANGED
@@ -148,7 +148,7 @@ class DB {
148
148
  async getNewClient() {
149
149
  return this.q.newClient();
150
150
  }
151
- async getSQLiteClient() {
151
+ getSQLiteClient() {
152
152
  if (this.db.dialect == Dialect.Postgres) {
153
153
  throw new Error(`can't call getSQLiteClient when dialect is postgres`);
154
154
  }
package/core/ent.d.ts CHANGED
@@ -5,14 +5,24 @@ import * as clause from "./clause";
5
5
  import { Builder } from "../action";
6
6
  import DataLoader from "dataloader";
7
7
  import { GlobalSchema } from "../schema/";
8
+ export declare function getEntKey<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, id: ID, options: LoadEntOptions<TEnt, TViewer>): string;
8
9
  export declare function loadEnt<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, id: ID, options: LoadEntOptions<TEnt, TViewer>): Promise<TEnt | null>;
9
10
  export declare function loadEntViaKey<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, key: any, options: LoadEntOptions<TEnt, TViewer>): Promise<TEnt | null>;
10
11
  export declare function loadEntX<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, id: ID, options: LoadEntOptions<TEnt, TViewer>): Promise<TEnt>;
11
12
  export declare function loadEntXViaKey<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, key: any, options: LoadEntOptions<TEnt, TViewer>): Promise<TEnt>;
13
+ /**
14
+ * @deprecated use loadCustomEnts
15
+ */
12
16
  export declare function loadEntFromClause<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, options: LoadEntOptions<TEnt, TViewer>, clause: clause.Clause): Promise<TEnt | null>;
17
+ /**
18
+ * @deprecated use loadCustomEnts
19
+ */
13
20
  export declare function loadEntXFromClause<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, options: LoadEntOptions<TEnt, TViewer>, clause: clause.Clause): Promise<TEnt>;
14
21
  export declare function loadEnts<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, options: LoadEntOptions<TEnt, TViewer>, ...ids: ID[]): Promise<Map<ID, TEnt>>;
15
22
  export declare function loadEntsList<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, options: LoadEntOptions<TEnt, TViewer>, ...ids: ID[]): Promise<TEnt[]>;
23
+ /**
24
+ * @deperecated use loadCustomEnts
25
+ */
16
26
  export declare function loadEntsFromClause<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, clause: clause.Clause, options: LoadEntOptions<TEnt, TViewer>): Promise<Map<ID, TEnt>>;
17
27
  export declare function loadCustomEnts<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, options: LoadCustomEntOptions<TEnt, TViewer>, query: CustomQuery): Promise<TEnt[]>;
18
28
  interface parameterizedQueryOptions {
@@ -79,6 +89,12 @@ export interface EditNodeOptions<T extends Ent> extends EditRowOptions {
79
89
  placeholderID?: ID;
80
90
  key: string;
81
91
  }
92
+ export declare class RawQueryOperation implements DataOperation {
93
+ private queries;
94
+ constructor(queries: (string | parameterizedQueryOptions)[]);
95
+ performWrite(queryer: Queryer, context?: Context): Promise<void>;
96
+ performWriteSync(queryer: SyncQueryer, context?: Context): void;
97
+ }
82
98
  export declare class EditNodeOperation<T extends Ent> implements DataOperation {
83
99
  options: EditNodeOptions<T>;
84
100
  private existingEnt;
@@ -210,7 +226,6 @@ interface loadEdgeForIDOptions<T extends AssocEdge> extends loadCustomEdgesOptio
210
226
  export declare function loadEdgeForID2<T extends AssocEdge>(options: loadEdgeForIDOptions<T>): Promise<T | undefined>;
211
227
  export declare function loadNodesByEdge<T extends Ent>(viewer: Viewer, id1: ID, edgeType: string, options: LoadEntOptions<T>): Promise<T[]>;
212
228
  export declare function applyPrivacyPolicyForRow<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, options: LoadEntOptions<TEnt, TViewer>, row: Data | null): Promise<TEnt | null>;
213
- export declare function applyPrivacyPolicyForRowX<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, options: LoadEntOptions<TEnt, TViewer>, row: Data): Promise<TEnt>;
214
229
  export declare function applyPrivacyPolicyForRows<TEnt extends Ent<TViewer>, TViewer extends Viewer>(viewer: TViewer, rows: Data[], options: LoadEntOptions<TEnt, TViewer>): Promise<Map<ID, TEnt>>;
215
230
  export declare function getEdgeTypeInGroup<T extends string>(viewer: Viewer, id1: ID, id2: ID, m: Map<T, string>): Promise<[T, AssocEdge] | undefined>;
216
231
  export {};
package/core/ent.js CHANGED
@@ -22,8 +22,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
22
22
  return (mod && mod.__esModule) ? mod : { "default": mod };
23
23
  };
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.applyPrivacyPolicyForRow = exports.loadNodesByEdge = exports.loadEdgeForID2 = exports.loadRawEdgeCountX = exports.loadUniqueNode = exports.loadUniqueEdge = exports.loadCustomEdges = exports.getEdgeClauseAndFields = exports.loadEdges = exports.defaultEdgeQueryOptions = exports.DefaultLimit = exports.loadEdgeDatas = exports.loadEdgeData = exports.assocEdgeLoader = exports.AssocEdgeData = exports.getCursor = exports.AssocEdge = exports.DeleteNodeOperation = exports.deleteRowsSync = exports.deleteRows = exports.editRowSync = exports.editRow = exports.buildUpdateQuery = exports.createRowSync = exports.createRow = exports.buildInsertQuery = exports.EdgeOperation = exports.__hasGlobalSchema = exports.clearGlobalSchema = exports.setGlobalSchema = exports.EditNodeOperation = exports.buildGroupQuery = exports.buildQuery = exports.loadRows = exports.performRawQuery = exports.loadRow = exports.loadRowX = exports.loadDerivedEntX = exports.loadDerivedEnt = exports.loadCustomData = exports.loadCustomEnts = exports.loadEntsFromClause = exports.loadEntsList = exports.loadEnts = exports.loadEntXFromClause = exports.loadEntFromClause = exports.loadEntXViaKey = exports.loadEntX = exports.loadEntViaKey = exports.loadEnt = void 0;
26
- exports.getEdgeTypeInGroup = exports.applyPrivacyPolicyForRows = exports.applyPrivacyPolicyForRowX = void 0;
25
+ exports.loadEdgeForID2 = exports.loadRawEdgeCountX = exports.loadUniqueNode = exports.loadUniqueEdge = exports.loadCustomEdges = exports.getEdgeClauseAndFields = exports.loadEdges = exports.defaultEdgeQueryOptions = exports.DefaultLimit = exports.loadEdgeDatas = exports.loadEdgeData = exports.assocEdgeLoader = exports.AssocEdgeData = exports.getCursor = exports.AssocEdge = exports.DeleteNodeOperation = exports.deleteRowsSync = exports.deleteRows = exports.editRowSync = exports.editRow = exports.buildUpdateQuery = exports.createRowSync = exports.createRow = exports.buildInsertQuery = exports.EdgeOperation = exports.__hasGlobalSchema = exports.clearGlobalSchema = exports.setGlobalSchema = exports.EditNodeOperation = exports.RawQueryOperation = exports.buildGroupQuery = exports.buildQuery = exports.loadRows = exports.performRawQuery = exports.loadRow = exports.loadRowX = exports.loadDerivedEntX = exports.loadDerivedEnt = exports.loadCustomData = exports.loadCustomEnts = exports.loadEntsFromClause = exports.loadEntsList = exports.loadEnts = exports.loadEntXFromClause = exports.loadEntFromClause = exports.loadEntXViaKey = exports.loadEntX = exports.loadEntViaKey = exports.loadEnt = exports.getEntKey = void 0;
26
+ exports.getEdgeTypeInGroup = exports.applyPrivacyPolicyForRows = exports.applyPrivacyPolicyForRow = exports.loadNodesByEdge = void 0;
27
27
  const db_1 = __importStar(require("./db"));
28
28
  const privacy_1 = require("./privacy");
29
29
  const clause = __importStar(require("./clause"));
@@ -87,10 +87,58 @@ function createDataLoader(options) {
87
87
  return result;
88
88
  }, loaderOptions);
89
89
  }
90
+ function getEntKey(viewer, id, options) {
91
+ return `${viewer.instanceKey()}:${options.loaderFactory.name}:${id}`;
92
+ }
93
+ exports.getEntKey = getEntKey;
94
+ // fetches the ent from cache if cache exists.
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
+ });
106
+ }
107
+ return {
108
+ key,
109
+ ent: r instanceof Error ? undefined : r,
110
+ error: r instanceof Error ? r : undefined,
111
+ cache,
112
+ };
113
+ }
90
114
  // Ent accessors
115
+ async function applyPrivacyPolicyForRowAndStoreInCache(viewer, options, row, info) {
116
+ const ent = await applyPrivacyPolicyForRow(viewer, options, row);
117
+ if (info.cache && info.key) {
118
+ info.cache.set(info.key, ent);
119
+ }
120
+ return ent instanceof Error ? null : ent;
121
+ }
122
+ async function applyPrivacyPolicyForRowAndStoreInCacheX(viewer, options, row, info) {
123
+ const ent = await applyPrivacyPolicyForRowImpl(viewer, options, row);
124
+ if (info.cache && info.key) {
125
+ info.cache.set(info.key, ent);
126
+ }
127
+ if (ent instanceof Error) {
128
+ throw ent;
129
+ }
130
+ if (ent === null) {
131
+ throw new Error(`applyPrivacyPolicyForRowImpl returned null when it shouldn't. ent error`);
132
+ }
133
+ return ent;
134
+ }
91
135
  async function loadEnt(viewer, id, options) {
136
+ const info = entFromCacheMaybe(viewer, id, options);
137
+ if (info.ent !== undefined) {
138
+ return info.ent;
139
+ }
92
140
  const row = await options.loaderFactory.createLoader(viewer.context).load(id);
93
- return await applyPrivacyPolicyForRow(viewer, options, row);
141
+ return applyPrivacyPolicyForRowAndStoreInCache(viewer, options, row, info);
94
142
  }
95
143
  exports.loadEnt = loadEnt;
96
144
  // this is the same implementation-wise (right now) as loadEnt. it's just clearer that it's not loaded via ID.
@@ -99,16 +147,32 @@ async function loadEntViaKey(viewer, key, options) {
99
147
  const row = await options.loaderFactory
100
148
  .createLoader(viewer.context)
101
149
  .load(key);
102
- return await applyPrivacyPolicyForRow(viewer, options, row);
150
+ if (!row) {
151
+ return null;
152
+ }
153
+ // TODO every row.id needs to be audited...
154
+ // https://github.com/lolopinto/ent/issues/1064
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);
103
160
  }
104
161
  exports.loadEntViaKey = loadEntViaKey;
105
162
  async function loadEntX(viewer, id, options) {
163
+ const info = entFromCacheMaybe(viewer, id, options);
164
+ if (info.error !== undefined) {
165
+ throw info.error;
166
+ }
167
+ if (info.ent !== undefined && info.ent !== null) {
168
+ return info.ent;
169
+ }
106
170
  const row = await options.loaderFactory.createLoader(viewer.context).load(id);
107
171
  if (!row) {
108
172
  // todo make this better
109
173
  throw new Error(`${options.loaderFactory.name}: couldn't find row for value ${id}`);
110
174
  }
111
- return await applyPrivacyPolicyForRowX(viewer, options, row);
175
+ return applyPrivacyPolicyForRowAndStoreInCacheX(viewer, options, row, info);
112
176
  }
113
177
  exports.loadEntX = loadEntX;
114
178
  async function loadEntXViaKey(viewer, key, options) {
@@ -119,9 +183,19 @@ async function loadEntXViaKey(viewer, key, options) {
119
183
  // todo make this better
120
184
  throw new Error(`${options.loaderFactory.name}: couldn't find row for value ${key}`);
121
185
  }
122
- return await applyPrivacyPolicyForRowX(viewer, options, row);
186
+ const info = entFromCacheMaybe(viewer, row.id, options);
187
+ if (info.error !== undefined) {
188
+ throw info.error;
189
+ }
190
+ if (info.ent !== undefined && info.ent !== null) {
191
+ return info.ent;
192
+ }
193
+ return applyPrivacyPolicyForRowAndStoreInCacheX(viewer, options, row, info);
123
194
  }
124
195
  exports.loadEntXViaKey = loadEntXViaKey;
196
+ /**
197
+ * @deprecated use loadCustomEnts
198
+ */
125
199
  async function loadEntFromClause(viewer, options, clause) {
126
200
  const rowOptions = {
127
201
  ...options,
@@ -129,12 +203,15 @@ async function loadEntFromClause(viewer, options, clause) {
129
203
  context: viewer.context,
130
204
  };
131
205
  const row = await loadRow(rowOptions);
132
- return await applyPrivacyPolicyForRow(viewer, options, row);
206
+ return applyPrivacyPolicyForRow(viewer, options, row);
133
207
  }
134
208
  exports.loadEntFromClause = loadEntFromClause;
135
209
  // same as loadEntFromClause
136
210
  // only works for ents where primary key is "id"
137
211
  // use loadEnt with a loaderFactory if different
212
+ /**
213
+ * @deprecated use loadCustomEnts
214
+ */
138
215
  async function loadEntXFromClause(viewer, options, clause) {
139
216
  const rowOptions = {
140
217
  ...options,
@@ -149,37 +226,66 @@ async function loadEnts(viewer, options, ...ids) {
149
226
  if (!ids.length) {
150
227
  return new Map();
151
228
  }
152
- let loaded = false;
153
- let rows = [];
154
- // TODO loadMany everywhere
155
- const l = options.loaderFactory.createLoader(viewer.context);
156
- if (l.loadMany) {
157
- loaded = true;
158
- rows = await l.loadMany(ids);
159
- }
160
- // TODO rewrite all of this
229
+ // result
161
230
  let m = new Map();
162
- if (loaded) {
163
- let rows2 = [];
164
- for (const row of rows) {
165
- if (!row) {
166
- continue;
231
+ const cache = viewer.context?.cache?.getEntCache();
232
+ let toFetch = [];
233
+ if (cache) {
234
+ for (const id of ids) {
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);
167
248
  }
168
- if (row instanceof Error) {
169
- throw row;
249
+ else {
250
+ toFetch.push(id);
170
251
  }
171
- rows2.push(row);
172
252
  }
173
- m = await applyPrivacyPolicyForRows(viewer, rows2, options);
174
253
  }
175
254
  else {
176
- m = await loadEntsFromClause(viewer,
177
- // this is always "id" if not using an ObjectLoaderFactory
178
- clause.In("id", ...ids), options);
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) {
266
+ continue;
267
+ }
268
+ if (row instanceof Error) {
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
+ }
179
287
  }
180
288
  return m;
181
- // TODO do we want to change this to be a map not a list so that it's easy to check for existence?
182
- // TODO eventually this should be doing a cache then db queyr and maybe depend on dataloader to get all the results at once
183
289
  }
184
290
  exports.loadEnts = loadEnts;
185
291
  // calls loadEnts and returns the results sorted in the order they were passed in
@@ -198,6 +304,9 @@ async function loadEntsList(viewer, options, ...ids) {
198
304
  exports.loadEntsList = loadEntsList;
199
305
  // we return a map here so that any sorting for queries that exist
200
306
  // can be done in O(N) time
307
+ /**
308
+ * @deperecated use loadCustomEnts
309
+ */
201
310
  async function loadEntsFromClause(viewer, clause, options) {
202
311
  const rowOptions = {
203
312
  ...options,
@@ -212,8 +321,19 @@ async function loadCustomEnts(viewer, options, query) {
212
321
  const rows = await loadCustomData(options, query, viewer.context);
213
322
  const result = new Array(rows.length);
214
323
  await Promise.all(rows.map(async (row, idx) => {
215
- const ent = new options.ent(viewer, row);
216
- let privacyEnt = await applyPrivacyPolicyForEnt(viewer, ent, row, options);
324
+ // TODO what if key is different
325
+ // TODO https://github.com/lolopinto/ent/issues/1064
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;
334
+ return;
335
+ }
336
+ const privacyEnt = await applyPrivacyPolicyForRowAndStoreInCache(viewer, options, row, info);
217
337
  if (privacyEnt) {
218
338
  result[idx] = privacyEnt;
219
339
  }
@@ -254,11 +374,25 @@ function isParameterizedQuery(opts) {
254
374
  * }) // doesn't change the query
255
375
  */
256
376
  async function loadCustomData(options, query, context) {
257
- function getClause(cls) {
258
- if (options.clause && options.loaderFactory?.options?.clause) {
259
- throw new Error(`cannot pass both options.clause && optsions.loaderFactory.options.clause`);
377
+ const rows = await loadCustomDataImpl(options, query, context);
378
+ // prime the data so that subsequent fetches of the row with this id are a cache hit.
379
+ if (options.prime) {
380
+ const loader = options.loaderFactory.createLoader(context);
381
+ if (isPrimableLoader(loader) && loader.primeAll !== undefined) {
382
+ for (const row of rows) {
383
+ loader.primeAll(row);
384
+ }
260
385
  }
261
- let optClause = options.clause || options.loaderFactory?.options?.clause;
386
+ }
387
+ return rows;
388
+ }
389
+ exports.loadCustomData = loadCustomData;
390
+ function isPrimableLoader(loader) {
391
+ return loader != undefined;
392
+ }
393
+ async function loadCustomDataImpl(options, query, context) {
394
+ function getClause(cls) {
395
+ let optClause = options.loaderFactory?.options?.clause;
262
396
  if (typeof optClause === "function") {
263
397
  optClause = optClause();
264
398
  }
@@ -269,7 +403,7 @@ async function loadCustomData(options, query, context) {
269
403
  }
270
404
  if (typeof query === "string") {
271
405
  // no caching, perform raw query
272
- return await performRawQuery(query, [], []);
406
+ return performRawQuery(query, [], []);
273
407
  }
274
408
  else if (isClause(query)) {
275
409
  // if a Clause is passed in and we have a default clause
@@ -277,7 +411,7 @@ async function loadCustomData(options, query, context) {
277
411
  // if we want to disableTransformations, need to indicate that with
278
412
  // disableTransformations option
279
413
  // this will have rudimentary caching but nothing crazy
280
- return await loadRows({
414
+ return loadRows({
281
415
  ...options,
282
416
  clause: getClause(query),
283
417
  context: context,
@@ -285,7 +419,7 @@ async function loadCustomData(options, query, context) {
285
419
  }
286
420
  else if (isParameterizedQuery(query)) {
287
421
  // no caching, perform raw query
288
- return await performRawQuery(query.query, query.values || [], query.logValues);
422
+ return performRawQuery(query.query, query.values || [], query.logValues);
289
423
  }
290
424
  else {
291
425
  let cls = query.clause;
@@ -293,7 +427,7 @@ async function loadCustomData(options, query, context) {
293
427
  cls = getClause(cls);
294
428
  }
295
429
  // this will have rudimentary caching but nothing crazy
296
- return await loadRows({
430
+ return loadRows({
297
431
  ...query,
298
432
  ...options,
299
433
  context: context,
@@ -301,15 +435,20 @@ async function loadCustomData(options, query, context) {
301
435
  });
302
436
  }
303
437
  }
304
- exports.loadCustomData = loadCustomData;
305
438
  // Derived ents
439
+ // no ent caching
306
440
  async function loadDerivedEnt(viewer, data, loader) {
307
441
  const ent = new loader(viewer, data);
308
- return await applyPrivacyPolicyForEnt(viewer, ent, data, {
442
+ const r = await applyPrivacyPolicyForEnt(viewer, ent, data, {
309
443
  ent: loader,
310
444
  });
445
+ if (r instanceof Error) {
446
+ return null;
447
+ }
448
+ return r;
311
449
  }
312
450
  exports.loadDerivedEnt = loadDerivedEnt;
451
+ // won't have caching yet either
313
452
  async function loadDerivedEntX(viewer, data, loader) {
314
453
  const ent = new loader(viewer, data);
315
454
  return await applyPrivacyPolicyForEntX(viewer, ent, data, { ent: loader });
@@ -319,18 +458,23 @@ exports.loadDerivedEntX = loadDerivedEntX;
319
458
  // TODO is there a smarter way to not instantiate two objects here?
320
459
  async function applyPrivacyPolicyForEnt(viewer, ent, data, fieldPrivacyOptions) {
321
460
  if (ent) {
322
- const visible = await (0, privacy_1.applyPrivacyPolicy)(viewer, ent.getPrivacyPolicy(), ent);
323
- if (!visible) {
324
- return null;
461
+ const error = await (0, privacy_1.applyPrivacyPolicyImpl)(viewer, ent.getPrivacyPolicy(), ent);
462
+ if (error === null) {
463
+ return doFieldPrivacy(viewer, ent, data, fieldPrivacyOptions);
325
464
  }
326
- return doFieldPrivacy(viewer, ent, data, fieldPrivacyOptions);
465
+ return error;
327
466
  }
328
467
  return null;
329
468
  }
330
469
  async function applyPrivacyPolicyForEntX(viewer, ent, data, options) {
331
- // this will throw
332
- await (0, privacy_1.applyPrivacyPolicyX)(viewer, ent.getPrivacyPolicy(), ent);
333
- return doFieldPrivacy(viewer, ent, data, options);
470
+ const r = await applyPrivacyPolicyForEnt(viewer, ent, data, options);
471
+ if (r instanceof Error) {
472
+ throw r;
473
+ }
474
+ if (r === null) {
475
+ throw new Error(`couldn't apply privacyPoliy for ent ${ent.id}`);
476
+ }
477
+ return r;
334
478
  }
335
479
  async function doFieldPrivacy(viewer, ent, data, options) {
336
480
  if (!options.fieldPrivacy) {
@@ -339,12 +483,12 @@ async function doFieldPrivacy(viewer, ent, data, options) {
339
483
  const promises = [];
340
484
  let somethingChanged = false;
341
485
  for (const [k, policy] of options.fieldPrivacy) {
486
+ const curr = data[k];
487
+ if (curr === null || curr === undefined) {
488
+ continue;
489
+ }
342
490
  promises.push((async () => {
343
491
  // don't do anything if key is null or for some reason missing
344
- const curr = data[k];
345
- if (curr === null || curr === undefined) {
346
- return;
347
- }
348
492
  const r = await (0, privacy_1.applyPrivacyPolicy)(viewer, policy, ent);
349
493
  if (!r) {
350
494
  data[k] = null;
@@ -388,42 +532,27 @@ async function loadRow(options) {
388
532
  }
389
533
  const query = buildQuery(options);
390
534
  logQuery(query, options.clause.logValues());
391
- try {
392
- const pool = db_1.default.getInstance().getPool();
393
- const res = await pool.query(query, options.clause.values());
394
- if (res.rowCount != 1) {
395
- if (res.rowCount > 1) {
396
- (0, logger_1.log)("error", "got more than one row for query " + query);
397
- }
398
- return null;
399
- }
400
- // put the row in the cache...
401
- if (cache) {
402
- cache.primeCache(options, res.rows[0]);
535
+ const pool = db_1.default.getInstance().getPool();
536
+ const res = await pool.query(query, options.clause.values());
537
+ if (res.rowCount != 1) {
538
+ if (res.rowCount > 1) {
539
+ (0, logger_1.log)("error", "got more than one row for query " + query);
403
540
  }
404
- return res.rows[0];
405
- }
406
- catch (e) {
407
- // an example of an error being suppressed
408
- // another one. TODO https://github.com/lolopinto/ent/issues/862
409
- (0, logger_1.log)("error", e);
410
541
  return null;
411
542
  }
543
+ // put the row in the cache...
544
+ if (cache) {
545
+ cache.primeCache(options, res.rows[0]);
546
+ }
547
+ return res.rows[0];
412
548
  }
413
549
  exports.loadRow = loadRow;
414
550
  // this always goes to the db, no cache, nothing
415
551
  async function performRawQuery(query, values, logValues) {
416
552
  const pool = db_1.default.getInstance().getPool();
417
553
  logQuery(query, logValues || []);
418
- try {
419
- const res = await pool.queryAll(query, values);
420
- return res.rows;
421
- }
422
- catch (e) {
423
- // TODO need to change every query to catch an error!
424
- (0, logger_1.log)("error", e);
425
- return [];
426
- }
554
+ const res = await pool.queryAll(query, values);
555
+ return res.rows;
427
556
  }
428
557
  exports.performRawQuery = performRawQuery;
429
558
  // TODO this should throw, we can't be hiding errors here
@@ -481,6 +610,36 @@ function buildGroupQuery(options) {
481
610
  ];
482
611
  }
483
612
  exports.buildGroupQuery = buildGroupQuery;
613
+ class RawQueryOperation {
614
+ constructor(queries) {
615
+ this.queries = queries;
616
+ }
617
+ async performWrite(queryer, context) {
618
+ for (const q of this.queries) {
619
+ if (typeof q === "string") {
620
+ logQuery(q, []);
621
+ await queryer.query(q);
622
+ }
623
+ else {
624
+ logQuery(q.query, q.logValues || []);
625
+ await queryer.query(q.query, q.values);
626
+ }
627
+ }
628
+ }
629
+ performWriteSync(queryer, context) {
630
+ for (const q of this.queries) {
631
+ if (typeof q === "string") {
632
+ logQuery(q, []);
633
+ queryer.execSync(q);
634
+ }
635
+ else {
636
+ logQuery(q.query, q.logValues || []);
637
+ queryer.execSync(q.query, q.values);
638
+ }
639
+ }
640
+ }
641
+ }
642
+ exports.RawQueryOperation = RawQueryOperation;
484
643
  class EditNodeOperation {
485
644
  constructor(options, existingEnt = null) {
486
645
  this.options = options;
@@ -547,7 +706,7 @@ class EditNodeOperation {
547
706
  optionClause = opts.clause;
548
707
  }
549
708
  if (optionClause) {
550
- cls = clause.And(optionClause, cls);
709
+ cls = clause.And(cls, optionClause);
551
710
  }
552
711
  }
553
712
  const query = buildQuery({
@@ -949,40 +1108,26 @@ function isSyncQueryer(queryer) {
949
1108
  async function mutateRow(queryer, query, values, logValues, options) {
950
1109
  logQuery(query, logValues);
951
1110
  let cache = options.context?.cache;
952
- try {
953
- let res;
954
- if (isSyncQueryer(queryer)) {
955
- res = queryer.execSync(query, values);
956
- }
957
- else {
958
- res = await queryer.exec(query, values);
959
- }
960
- if (cache) {
961
- cache.clearCache();
962
- }
963
- return res;
1111
+ let res;
1112
+ if (isSyncQueryer(queryer)) {
1113
+ res = queryer.execSync(query, values);
964
1114
  }
965
- catch (err) {
966
- // TODO:::why is this not rethrowing?
967
- (0, logger_1.log)("error", err);
968
- throw err;
1115
+ else {
1116
+ res = await queryer.exec(query, values);
969
1117
  }
1118
+ if (cache) {
1119
+ cache.clearCache();
1120
+ }
1121
+ return res;
970
1122
  }
971
1123
  function mutateRowSync(queryer, query, values, logValues, options) {
972
1124
  logQuery(query, logValues);
973
1125
  let cache = options.context?.cache;
974
- try {
975
- const res = queryer.execSync(query, values);
976
- if (cache) {
977
- cache.clearCache();
978
- }
979
- return res;
980
- }
981
- catch (err) {
982
- // TODO:::why is this not rethrowing?
983
- (0, logger_1.log)("error", err);
984
- throw err;
1126
+ const res = queryer.execSync(query, values);
1127
+ if (cache) {
1128
+ cache.clearCache();
985
1129
  }
1130
+ return res;
986
1131
  }
987
1132
  function buildInsertQuery(options, suffix) {
988
1133
  let fields = [];
@@ -1062,6 +1207,9 @@ function buildUpdateQuery(options, suffix) {
1062
1207
  let query = `UPDATE ${options.tableName} SET ${vals} WHERE `;
1063
1208
  query = query + options.whereClause.clause(idx);
1064
1209
  values.push(...options.whereClause.values());
1210
+ if (options.fieldsToLog) {
1211
+ logValues.push(...options.whereClause.logValues());
1212
+ }
1065
1213
  if (suffix) {
1066
1214
  query = query + " " + suffix;
1067
1215
  }
@@ -1352,18 +1500,24 @@ async function loadNodesByEdge(viewer, id1, edgeType, options) {
1352
1500
  }
1353
1501
  exports.loadNodesByEdge = loadNodesByEdge;
1354
1502
  async function applyPrivacyPolicyForRow(viewer, options, row) {
1503
+ const r = await applyPrivacyPolicyForRowImpl(viewer, options, row);
1504
+ return r instanceof Error ? null : r;
1505
+ }
1506
+ exports.applyPrivacyPolicyForRow = applyPrivacyPolicyForRow;
1507
+ async function applyPrivacyPolicyForRowImpl(viewer, options, row) {
1355
1508
  if (!row) {
1356
1509
  return null;
1357
1510
  }
1358
1511
  const ent = new options.ent(viewer, row);
1359
- return await applyPrivacyPolicyForEnt(viewer, ent, row, options);
1512
+ return applyPrivacyPolicyForEnt(viewer, ent, row, options);
1360
1513
  }
1361
- exports.applyPrivacyPolicyForRow = applyPrivacyPolicyForRow;
1362
1514
  async function applyPrivacyPolicyForRowX(viewer, options, row) {
1363
1515
  const ent = new options.ent(viewer, row);
1364
1516
  return await applyPrivacyPolicyForEntX(viewer, ent, row, options);
1365
1517
  }
1366
- exports.applyPrivacyPolicyForRowX = applyPrivacyPolicyForRowX;
1518
+ // TODO this needs to be changed to use ent cache as needed...
1519
+ // most current callsites fine not using it
1520
+ // custom_query is one that should be updated
1367
1521
  async function applyPrivacyPolicyForRows(viewer, rows, options) {
1368
1522
  let m = new Map();
1369
1523
  // apply privacy logic
@@ -13,6 +13,7 @@ export declare class ObjectLoader<T> implements Loader<T, Data | null> {
13
13
  clearAll(): void;
14
14
  loadMany(keys: T[]): Promise<Data[]>;
15
15
  prime(data: Data): void;
16
+ primeAll(data: Data): void;
16
17
  }
17
18
  interface ObjectLoaderOptions extends SelectDataOptions {
18
19
  instanceKey?: string;
@@ -53,7 +53,7 @@ function createDataLoader(options) {
53
53
  optionClause = options.clause;
54
54
  }
55
55
  if (optionClause) {
56
- cls = clause.And(optionClause, cls);
56
+ cls = clause.And(cls, optionClause);
57
57
  }
58
58
  }
59
59
  const rowOptions = {
@@ -71,6 +71,9 @@ function createDataLoader(options) {
71
71
  const rows = await (0, ent_1.loadRows)(rowOptions);
72
72
  for (const row of rows) {
73
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
+ }
74
77
  const idx = m.get(id);
75
78
  if (idx === undefined) {
76
79
  throw new Error(`malformed query. got ${id} back but didn't query for it`);
@@ -136,7 +139,7 @@ class ObjectLoader {
136
139
  optionClause = this.options.clause;
137
140
  }
138
141
  if (optionClause) {
139
- cls = clause.And(optionClause, cls);
142
+ cls = clause.And(cls, optionClause);
140
143
  }
141
144
  }
142
145
  const rowOptions = {
@@ -163,7 +166,7 @@ class ObjectLoader {
163
166
  optionClause = this.options.clause;
164
167
  }
165
168
  if (optionClause) {
166
- cls = clause.And(optionClause, cls);
169
+ cls = clause.And(cls, optionClause);
167
170
  }
168
171
  }
169
172
  const rowOptions = {
@@ -181,8 +184,23 @@ class ObjectLoader {
181
184
  this.loader.prime(key, data);
182
185
  }
183
186
  }
187
+ // prime this loader and any other loaders it's aware of
188
+ primeAll(data) {
189
+ this.prime(data);
190
+ if (this.primedLoaders) {
191
+ for (const [key, loader] of this.primedLoaders) {
192
+ const value = data[key];
193
+ if (value !== undefined) {
194
+ loader.prime(data);
195
+ }
196
+ }
197
+ }
198
+ }
184
199
  }
185
200
  exports.ObjectLoader = ObjectLoader;
201
+ // NOTE: if not querying for all columns
202
+ // have to query for the id field as one of the fields
203
+ // because it's used to maintain sort order of the queried ids
186
204
  class ObjectLoaderFactory {
187
205
  constructor(options) {
188
206
  this.options = options;
package/core/privacy.d.ts CHANGED
@@ -183,6 +183,7 @@ export declare class AllowIfSubPolicyAllowsRule implements PrivacyPolicyRule {
183
183
  }
184
184
  export declare function applyPrivacyPolicy(v: Viewer, policy: PrivacyPolicy, ent: Ent | undefined): Promise<boolean>;
185
185
  export declare function applyPrivacyPolicyX(v: Viewer, policy: PrivacyPolicy, ent: Ent | undefined, throwErr?: () => Error): Promise<boolean>;
186
+ export declare function applyPrivacyPolicyImpl(v: Viewer, policy: PrivacyPolicy, ent: Ent | undefined, throwErr?: () => Error): Promise<Error | null>;
186
187
  export declare const AlwaysAllowPrivacyPolicy: PrivacyPolicy;
187
188
  export declare const AlwaysDenyPrivacyPolicy: PrivacyPolicy;
188
189
  export declare const AllowIfViewerPrivacyPolicy: PrivacyPolicy;
package/core/privacy.js CHANGED
@@ -1,9 +1,8 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.AllowIfViewerHasIdentityPrivacyPolicy = exports.AllowIfViewerPrivacyPolicy = exports.AlwaysDenyPrivacyPolicy = exports.AlwaysAllowPrivacyPolicy = exports.applyPrivacyPolicyX = exports.applyPrivacyPolicy = exports.AllowIfSubPolicyAllowsRule = exports.DelayedResultRule = exports.AllowIfConditionAppliesRule = exports.DenyIfViewerOutboundEdgeDoesNotExistRule = exports.DenyIfViewerInboundEdgeDoesNotExistRule = exports.DenyIfEdgeDoesNotExistRule = exports.DenyIfViewerOutboundEdgeExistsRule = exports.DenyIfViewerInboundEdgeExistsRule = exports.DenyIfEdgeExistsRule = exports.AllowIfViewerOutboundEdgeExistsRule = exports.AllowIfViewerInboundEdgeExistsRule = exports.AllowIfEdgeExistsRule = exports.DenyIfEntIsNotVisibleRule = exports.DenyIfEntIsVisibleRule = exports.DenyIfEntIsVisiblePolicy = exports.AllowIfEntIsVisiblePolicy = exports.AllowIfEntIsNotVisibleRule = exports.AllowIfEntIsVisibleRule = exports.DenyIfEntPropertyIsRule = exports.AllowIfEntPropertyIsRule = exports.AllowIfViewerIsEntPropertyRule = exports.AllowIfViewerIsRule = exports.DenyIfFuncRule = exports.AllowIfFuncRule = exports.DenyIfViewerEqualsRule = exports.AllowIfViewerEqualsRule = exports.AllowIfViewerRule = exports.AllowIfHasIdentity = exports.DenyIfLoggedInRule = exports.DenyIfLoggedOutRule = exports.AlwaysDenyRule = exports.AlwaysAllowRule = exports.EntPrivacyError = void 0;
3
+ exports.AllowIfViewerHasIdentityPrivacyPolicy = exports.AllowIfViewerPrivacyPolicy = exports.AlwaysDenyPrivacyPolicy = exports.AlwaysAllowPrivacyPolicy = exports.applyPrivacyPolicyImpl = exports.applyPrivacyPolicyX = exports.applyPrivacyPolicy = exports.AllowIfSubPolicyAllowsRule = exports.DelayedResultRule = exports.AllowIfConditionAppliesRule = exports.DenyIfViewerOutboundEdgeDoesNotExistRule = exports.DenyIfViewerInboundEdgeDoesNotExistRule = exports.DenyIfEdgeDoesNotExistRule = exports.DenyIfViewerOutboundEdgeExistsRule = exports.DenyIfViewerInboundEdgeExistsRule = exports.DenyIfEdgeExistsRule = exports.AllowIfViewerOutboundEdgeExistsRule = exports.AllowIfViewerInboundEdgeExistsRule = exports.AllowIfEdgeExistsRule = exports.DenyIfEntIsNotVisibleRule = exports.DenyIfEntIsVisibleRule = exports.DenyIfEntIsVisiblePolicy = exports.AllowIfEntIsVisiblePolicy = exports.AllowIfEntIsNotVisibleRule = exports.AllowIfEntIsVisibleRule = exports.DenyIfEntPropertyIsRule = exports.AllowIfEntPropertyIsRule = exports.AllowIfViewerIsEntPropertyRule = exports.AllowIfViewerIsRule = exports.DenyIfFuncRule = exports.AllowIfFuncRule = exports.DenyIfViewerEqualsRule = exports.AllowIfViewerEqualsRule = exports.AllowIfViewerRule = exports.AllowIfHasIdentity = exports.DenyIfLoggedInRule = exports.DenyIfLoggedOutRule = exports.AlwaysDenyRule = exports.AlwaysAllowRule = exports.EntPrivacyError = void 0;
4
4
  const base_1 = require("./base");
5
5
  const ent_1 = require("./ent");
6
- const logger_1 = require("./logger");
7
6
  // copied from ./base
8
7
  var privacyResult;
9
8
  (function (privacyResult) {
@@ -437,42 +436,42 @@ class AllowIfSubPolicyAllowsRule {
437
436
  }
438
437
  exports.AllowIfSubPolicyAllowsRule = AllowIfSubPolicyAllowsRule;
439
438
  async function applyPrivacyPolicy(v, policy, ent) {
440
- try {
441
- return await applyPrivacyPolicyX(v, policy, ent);
442
- }
443
- catch (e) {
444
- // TODO privacy errors should not throw
445
- // but other expected errors should throw...
446
- // we shouldn't just hide them
447
- (0, logger_1.log)("debug", e);
448
- return false;
449
- }
439
+ const err = await applyPrivacyPolicyImpl(v, policy, ent);
440
+ return err === null;
450
441
  }
451
442
  exports.applyPrivacyPolicy = applyPrivacyPolicy;
452
- // this will throw an exception if fails or return error | null?
453
443
  async function applyPrivacyPolicyX(v, policy, ent, throwErr) {
444
+ const err = await applyPrivacyPolicyImpl(v, policy, ent, throwErr);
445
+ if (err !== null) {
446
+ throw err;
447
+ }
448
+ return true;
449
+ }
450
+ exports.applyPrivacyPolicyX = applyPrivacyPolicyX;
451
+ // this will throw an exception if fails or return error | null?
452
+ async function applyPrivacyPolicyImpl(v, policy, ent, throwErr) {
454
453
  for (const rule of policy.rules) {
455
454
  const res = await rule.apply(v, ent);
456
455
  if (res.result == privacyResult.Allow) {
457
- return true;
456
+ return null;
458
457
  }
459
458
  else if (res.result == privacyResult.Deny) {
460
459
  // specific error throw that
461
460
  if (res.error) {
462
- throw res.error;
461
+ return res.error;
463
462
  }
464
463
  if (res.getError) {
465
- throw res.getError(policy, rule, ent);
464
+ return res.getError(policy, rule, ent);
466
465
  }
467
466
  if (throwErr) {
468
- throw throwErr();
467
+ return throwErr();
469
468
  }
470
- throw new EntPrivacyError(policy, rule, ent);
469
+ return new EntPrivacyError(policy, rule, ent);
471
470
  }
472
471
  }
473
- throw new EntInvalidPrivacyPolicyError(policy, ent);
472
+ return new EntInvalidPrivacyPolicyError(policy, ent);
474
473
  }
475
- exports.applyPrivacyPolicyX = applyPrivacyPolicyX;
474
+ exports.applyPrivacyPolicyImpl = applyPrivacyPolicyImpl;
476
475
  exports.AlwaysAllowPrivacyPolicy = {
477
476
  rules: [exports.AlwaysAllowRule],
478
477
  };
package/core/viewer.js CHANGED
@@ -35,7 +35,7 @@ class IDViewer {
35
35
  return this.ent;
36
36
  }
37
37
  instanceKey() {
38
- return `idViewer: ${this.viewerID}`;
38
+ return `idViewer:${this.viewerID}`;
39
39
  }
40
40
  }
41
41
  exports.IDViewer = IDViewer;
package/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  export * from "./core/base";
2
- export { loadEnt, loadCustomData, loadCustomEnts, loadEntX, loadEnts, CustomQuery, loadDerivedEnt, loadDerivedEntX, loadEntViaKey, loadEntXViaKey, performRawQuery, loadRowX, loadRow, loadRows, DataOperation, EditNodeOptions, EditNodeOperation, EdgeOperation, DeleteNodeOperation, AssocEdge, AssocEdgeInputOptions, AssocEdgeInput, AssocEdgeData, loadEdgeData, loadEdgeDatas, loadEdges, loadUniqueEdge, loadUniqueNode, loadRawEdgeCountX, loadEdgeForID2, loadNodesByEdge, getEdgeTypeInGroup, setGlobalSchema, } from "./core/ent";
2
+ export { loadEnt, loadCustomData, loadCustomEnts, loadEntX, loadEnts, CustomQuery, loadDerivedEnt, loadDerivedEntX, loadEntViaKey, loadEntXViaKey, performRawQuery, loadRowX, loadRow, loadRows, DataOperation, EditNodeOptions, EditNodeOperation, RawQueryOperation, EdgeOperation, DeleteNodeOperation, AssocEdge, AssocEdgeInputOptions, AssocEdgeInput, AssocEdgeData, loadEdgeData, loadEdgeDatas, loadEdges, loadUniqueEdge, loadUniqueNode, loadRawEdgeCountX, loadEdgeForID2, loadNodesByEdge, getEdgeTypeInGroup, setGlobalSchema, } from "./core/ent";
3
3
  import DB from "./core/db";
4
4
  export * from "./core/loaders";
5
5
  export { DB };
package/index.js CHANGED
@@ -25,8 +25,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
25
25
  return (mod && mod.__esModule) ? mod : { "default": mod };
26
26
  };
27
27
  Object.defineProperty(exports, "__esModule", { value: true });
28
- exports.DenyIfEdgeDoesNotExistRule = exports.DenyIfViewerOutboundEdgeExistsRule = exports.DenyIfViewerInboundEdgeExistsRule = exports.DenyIfEdgeExistsRule = exports.AllowIfViewerOutboundEdgeExistsRule = exports.AllowIfViewerInboundEdgeExistsRule = exports.AllowIfEdgeExistsRule = exports.DenyIfViewerEqualsRule = exports.AllowIfViewerEqualsRule = exports.DenyIfEntPropertyIsRule = exports.AllowIfEntPropertyIsRule = exports.AllowIfViewerIsEntPropertyRule = exports.AllowIfViewerIsRule = exports.AllowIfFuncRule = exports.AllowIfViewerRule = exports.AllowIfHasIdentity = exports.DenyIfLoggedOutRule = exports.DenyIfLoggedInRule = exports.AlwaysDenyRule = exports.AlwaysAllowRule = exports.EntPrivacyError = exports.DB = exports.setGlobalSchema = exports.getEdgeTypeInGroup = exports.loadNodesByEdge = exports.loadEdgeForID2 = exports.loadRawEdgeCountX = exports.loadUniqueNode = exports.loadUniqueEdge = exports.loadEdges = exports.loadEdgeDatas = exports.loadEdgeData = exports.AssocEdgeData = exports.AssocEdge = exports.DeleteNodeOperation = exports.EdgeOperation = exports.EditNodeOperation = exports.loadRows = exports.loadRow = exports.loadRowX = exports.performRawQuery = exports.loadEntXViaKey = exports.loadEntViaKey = exports.loadDerivedEntX = exports.loadDerivedEnt = exports.loadEnts = exports.loadEntX = exports.loadCustomEnts = exports.loadCustomData = exports.loadEnt = void 0;
29
- exports.setLogLevels = exports.loadConfig = exports.LoggedOutViewer = exports.IDViewer = exports.ContextCache = exports.query = exports.AllowIfViewerHasIdentityPrivacyPolicy = exports.AllowIfViewerPrivacyPolicy = exports.AllowIfSubPolicyAllowsRule = exports.AllowIfConditionAppliesRule = exports.AlwaysDenyPrivacyPolicy = exports.AlwaysAllowPrivacyPolicy = exports.applyPrivacyPolicyX = exports.applyPrivacyPolicy = exports.DelayedResultRule = exports.DenyIfEntIsVisiblePolicy = exports.AllowIfEntIsVisiblePolicy = exports.DenyIfEntIsNotVisibleRule = exports.DenyIfEntIsVisibleRule = exports.AllowIfEntIsNotVisibleRule = exports.AllowIfEntIsVisibleRule = exports.DenyIfViewerOutboundEdgeDoesNotExistRule = exports.DenyIfViewerInboundEdgeDoesNotExistRule = void 0;
28
+ exports.DenyIfViewerOutboundEdgeExistsRule = exports.DenyIfViewerInboundEdgeExistsRule = exports.DenyIfEdgeExistsRule = exports.AllowIfViewerOutboundEdgeExistsRule = exports.AllowIfViewerInboundEdgeExistsRule = exports.AllowIfEdgeExistsRule = exports.DenyIfViewerEqualsRule = exports.AllowIfViewerEqualsRule = exports.DenyIfEntPropertyIsRule = exports.AllowIfEntPropertyIsRule = exports.AllowIfViewerIsEntPropertyRule = exports.AllowIfViewerIsRule = exports.AllowIfFuncRule = exports.AllowIfViewerRule = exports.AllowIfHasIdentity = exports.DenyIfLoggedOutRule = exports.DenyIfLoggedInRule = exports.AlwaysDenyRule = exports.AlwaysAllowRule = exports.EntPrivacyError = exports.DB = exports.setGlobalSchema = exports.getEdgeTypeInGroup = exports.loadNodesByEdge = exports.loadEdgeForID2 = exports.loadRawEdgeCountX = exports.loadUniqueNode = exports.loadUniqueEdge = exports.loadEdges = exports.loadEdgeDatas = exports.loadEdgeData = exports.AssocEdgeData = exports.AssocEdge = exports.DeleteNodeOperation = exports.EdgeOperation = exports.RawQueryOperation = exports.EditNodeOperation = exports.loadRows = exports.loadRow = exports.loadRowX = exports.performRawQuery = exports.loadEntXViaKey = exports.loadEntViaKey = exports.loadDerivedEntX = exports.loadDerivedEnt = exports.loadEnts = exports.loadEntX = exports.loadCustomEnts = exports.loadCustomData = exports.loadEnt = void 0;
29
+ exports.setLogLevels = exports.loadConfig = exports.LoggedOutViewer = exports.IDViewer = exports.ContextCache = exports.query = exports.AllowIfViewerHasIdentityPrivacyPolicy = exports.AllowIfViewerPrivacyPolicy = exports.AllowIfSubPolicyAllowsRule = exports.AllowIfConditionAppliesRule = exports.AlwaysDenyPrivacyPolicy = exports.AlwaysAllowPrivacyPolicy = exports.applyPrivacyPolicyX = exports.applyPrivacyPolicy = exports.DelayedResultRule = exports.DenyIfEntIsVisiblePolicy = exports.AllowIfEntIsVisiblePolicy = exports.DenyIfEntIsNotVisibleRule = exports.DenyIfEntIsVisibleRule = exports.AllowIfEntIsNotVisibleRule = exports.AllowIfEntIsVisibleRule = exports.DenyIfViewerOutboundEdgeDoesNotExistRule = exports.DenyIfViewerInboundEdgeDoesNotExistRule = exports.DenyIfEdgeDoesNotExistRule = void 0;
30
30
  __exportStar(require("./core/base"), exports);
31
31
  var ent_1 = require("./core/ent");
32
32
  Object.defineProperty(exports, "loadEnt", { enumerable: true, get: function () { return ent_1.loadEnt; } });
@@ -44,6 +44,7 @@ Object.defineProperty(exports, "loadRowX", { enumerable: true, get: function ()
44
44
  Object.defineProperty(exports, "loadRow", { enumerable: true, get: function () { return ent_1.loadRow; } });
45
45
  Object.defineProperty(exports, "loadRows", { enumerable: true, get: function () { return ent_1.loadRows; } });
46
46
  Object.defineProperty(exports, "EditNodeOperation", { enumerable: true, get: function () { return ent_1.EditNodeOperation; } });
47
+ Object.defineProperty(exports, "RawQueryOperation", { enumerable: true, get: function () { return ent_1.RawQueryOperation; } });
47
48
  Object.defineProperty(exports, "EdgeOperation", { enumerable: true, get: function () { return ent_1.EdgeOperation; } });
48
49
  Object.defineProperty(exports, "DeleteNodeOperation", { enumerable: true, get: function () { return ent_1.DeleteNodeOperation; } });
49
50
  Object.defineProperty(exports, "AssocEdge", { enumerable: true, get: function () { return ent_1.AssocEdge; } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@snowtop/ent",
3
- "version": "0.1.0-alpha74",
3
+ "version": "0.1.0-alpha78",
4
4
  "description": "snowtop ent framework",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
@@ -1,5 +1,5 @@
1
1
  import { Data, Ent, LoaderInfo, PrivacyPolicy, Viewer } from "../core/base";
2
- import { Builder } from "../action/action";
2
+ import { Builder, Changeset } from "../action/action";
3
3
  import { Clause } from "../core/clause";
4
4
  import { AssocEdgeInput } from "../core/ent";
5
5
  export declare type FieldMap = {
@@ -79,7 +79,7 @@ export interface Pattern {
79
79
  disableMixin?: boolean;
80
80
  edges?: Edge[];
81
81
  transformRead?: () => Clause;
82
- transformWrite?: <T extends Ent>(stmt: UpdateOperation<T>) => TransformedUpdateOperation<T> | null;
82
+ transformWrite?: <T extends Ent<TViewer>, TViewer extends Viewer = Viewer>(stmt: UpdateOperation<T, TViewer>) => TransformedUpdateOperation<T, TViewer> | null;
83
83
  transformsDelete?: boolean;
84
84
  transformsInsert?: boolean;
85
85
  transformsUpdate?: boolean;
@@ -99,14 +99,15 @@ export interface TransformedEdgeUpdateOperation {
99
99
  }
100
100
  export interface UpdateOperation<TEnt extends Ent<TViewer>, TViewer extends Viewer = Viewer> {
101
101
  op: SQLStatementOperation;
102
- builder: Builder<TEnt, TViewer>;
102
+ builder: Builder<TEnt, TViewer, any>;
103
103
  input: Data;
104
104
  data?: Map<string, any>;
105
105
  }
106
- export interface TransformedUpdateOperation<T extends Ent> {
106
+ export interface TransformedUpdateOperation<T extends Ent<TViewer>, TViewer extends Viewer = Viewer> {
107
107
  op: SQLStatementOperation;
108
108
  data?: Data;
109
109
  existingEnt?: T | null;
110
+ changeset?(): Promise<Changeset> | Changeset;
110
111
  }
111
112
  export declare enum DBType {
112
113
  UUID = "UUID",
@@ -205,6 +206,7 @@ export interface FieldOptions {
205
206
  privacyPolicy?: PrivacyPolicy | (() => PrivacyPolicy);
206
207
  getDerivedFields?(name: string): FieldMap;
207
208
  convert?: ConvertType;
209
+ fetchOnDemand?: boolean;
208
210
  [x: string]: any;
209
211
  }
210
212
  export interface PolymorphicOptions {
@@ -1,16 +1,38 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5
+ }) : (function(o, m, k, k2) {
6
+ if (k2 === undefined) k2 = k;
7
+ o[k2] = m[k];
8
+ }));
9
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
11
+ }) : function(o, v) {
12
+ o["default"] = v;
13
+ });
14
+ var __importStar = (this && this.__importStar) || function (mod) {
15
+ if (mod && mod.__esModule) return mod;
16
+ var result = {};
17
+ if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18
+ __setModuleDefault(result, mod);
19
+ return result;
4
20
  };
5
21
  Object.defineProperty(exports, "__esModule", { value: true });
6
22
  exports.deleteRowsForTest = exports.editRowForTest = exports.createRowForTest = void 0;
7
23
  const ent_1 = require("../core/ent");
8
- const db_1 = __importDefault(require("../core/db"));
24
+ const db_1 = __importStar(require("../core/db"));
9
25
  function isSyncClient(client) {
10
26
  return client.execSync !== undefined;
11
27
  }
12
28
  async function createRowForTest(options, suffix) {
13
- const client = await db_1.default.getInstance().getNewClient();
29
+ let client;
30
+ if (db_1.Dialect.SQLite === db_1.default.getDialect()) {
31
+ client = db_1.default.getInstance().getSQLiteClient();
32
+ }
33
+ else {
34
+ client = await db_1.default.getInstance().getNewClient();
35
+ }
14
36
  try {
15
37
  if (isSyncClient(client)) {
16
38
  return (0, ent_1.createRowSync)(client, options, suffix || "");