• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

panates / opra / 28585340608

02 Jul 2026 09:58AM UTC coverage: 82.799% (-0.3%) from 83.077%
28585340608

push

github

erayhanoglu
Feat: Added bundle support for HTTP protocol
Refactor: Renamed HttpIncoming to HttpRequest, HttpOutgoing to HttpResponse
Refactor: Refactored MultipartReader to support multipart/mixed

3763 of 4860 branches covered (77.43%)

Branch coverage included in aggregate %.

1755 of 1981 new or added lines in 54 files covered. (88.59%)

94 existing lines in 4 files now uncovered.

33673 of 40353 relevant lines covered (83.45%)

220.83 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.04
/packages/common/src/document/data-type/complex-type-base.ts
1
import hashObject from 'object-hash';
1✔
2
import { asMutable, type StrictOmit, type Type } from 'ts-gems';
1✔
3
import { type Validator, validator, vg } from 'valgen';
1✔
4
import {
1✔
5
  FieldsProjection,
1✔
6
  parseFieldsProjection,
1✔
7
  ResponsiveMap,
1✔
8
} from '../../helpers/index.js';
1✔
9
import { OpraSchema } from '../../schema/index.js';
1✔
10
import type { DocumentElement } from '../common/document-element.js';
1✔
11
import { DocumentInitContext } from '../common/document-init-context.js';
1✔
12
import type { ApiField } from './api-field.js';
1✔
13
import type { ComplexType } from './complex-type.js';
1✔
14
import { DataType } from './data-type.js';
1✔
15

1✔
16
export const FIELD_PATH_PATTERN = /^([+-])?([a-z$_][\w.]*)$/i;
1✔
17

1✔
18
/**
1✔
19
 * Type definition of class constructor for ComplexTypeBase
1✔
20
 * @class ComplexTypeBase
1✔
21
 */
1✔
22
interface ComplexTypeBaseStatic {
1✔
23
  /**
1✔
24
   * Class constructor of MappedType
1✔
25
   *
1✔
26
   * @param owner
1✔
27
   * @param initArgs
1✔
28
   * @param context
1✔
29
   * @constructor
1✔
30
   */
1✔
31
  new (
1✔
32
    owner: DocumentElement,
1✔
33
    initArgs: DataType.InitArguments,
1✔
34
    context?: DocumentInitContext,
1✔
35
  ): ComplexTypeBase;
1✔
36

1✔
37
  prototype: ComplexTypeBase;
1✔
38
}
1✔
39

1✔
40
/**
1✔
41
 * Type definition of ComplexTypeBase prototype
1✔
42
 * @interface ComplexTypeBase
1✔
43
 */
1✔
44
export interface ComplexTypeBase extends ComplexTypeBaseClass {}
1✔
45

1✔
46
/**
1✔
47
 *
1✔
48
 * @constructor
1✔
49
 */
1✔
50
export const ComplexTypeBase = function (
1✔
51
  this: ComplexTypeBase | void,
636✔
52
  ...args: any[]
636✔
53
) {
636✔
54
  if (!this)
636✔
55
    throw new TypeError('"this" should be passed to call class constructor');
636!
56
  // Constructor
636✔
57
  const [owner, initArgs, context] = args as [
636✔
58
    DocumentElement,
636✔
59
    ComplexType.InitArguments,
636✔
60
    DocumentInitContext | undefined,
636✔
61
  ];
636✔
62
  DataType.call(this, owner, initArgs, context);
636✔
63
  const _this = asMutable(this);
636✔
64
  (_this as any)._fields = new ResponsiveMap();
636✔
65
} as Function as ComplexTypeBaseStatic;
636✔
66

1✔
67
/**
1✔
68
 *
1✔
69
 */
1✔
70
abstract class ComplexTypeBaseClass extends DataType {
1✔
71
  readonly ctor?: Type;
1!
72
  declare protected _fields: ResponsiveMap<ApiField>;
×
73
  readonly additionalFields?:
×
NEW
74
    boolean | DataType | ['error'] | ['error', string];
×
75
  readonly keyField?: OpraSchema.Field.Name;
×
76

1✔
77
  fieldCount(scope?: string): number {
1✔
78
    if (scope === '*') return this._fields.size;
1!
79
    let count = 0;
1✔
80
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
1✔
81
    for (const i of this.fields(scope)) count++;
1✔
82
    return count;
1✔
83
  }
1✔
84

1✔
85
  fieldEntries(scope?: string): IterableIterator<[string, ApiField]> {
1✔
86
    let iterator: IterableIterator<[string, ApiField]> | undefined =
1,031✔
87
      this._fields.entries();
1,031✔
88
    if (scope === '*') return iterator;
1,031✔
89
    let r: IteratorResult<[string, ApiField]>;
247✔
90
    return {
247✔
91
      next() {
247✔
92
        while (iterator) {
3,723✔
93
          r = iterator.next();
3,833✔
94
          if (r.done) break;
3,833✔
95
          if (r.value && r.value[1].inScope(scope)) break;
3,833✔
96
        }
3,833✔
97
        if (r.done) return { done: r.done, value: undefined };
3,723✔
98
        return {
3,476✔
99
          done: r.done,
3,476✔
100
          value: [r.value[0], r.value[1].forScope(scope)],
3,476✔
101
        };
3,476✔
102
      },
3,476✔
103
      return(value?: [string, ApiField]) {
247✔
104
        iterator = undefined;
×
105
        return { done: true, value };
×
106
      },
×
107
      [Symbol.iterator]() {
247✔
108
        return this;
×
109
      },
×
110
    };
247✔
111
  }
247✔
112

1✔
113
  fields(scope?: string): IterableIterator<ApiField> {
1✔
114
    let iterator: IterableIterator<[string, ApiField]> | undefined =
968✔
115
      this.fieldEntries(scope);
968✔
116
    let r: IteratorResult<[string, ApiField]>;
968✔
117
    return {
968✔
118
      next() {
968✔
119
        if (!iterator) return { done: true, value: undefined };
9,991!
120
        r = iterator!.next();
9,991✔
121
        return { done: r.done, value: r.value?.[1] };
9,991✔
122
      },
9,991✔
123
      return(value?: ApiField) {
968✔
124
        iterator = undefined;
×
125
        return { done: true, value };
×
126
      },
×
127
      [Symbol.iterator]() {
968✔
128
        return this;
968✔
129
      },
968✔
130
    };
968✔
131
  }
968✔
132

1✔
133
  fieldNames(scope?: string): IterableIterator<string> {
1✔
134
    if (scope === '*') return this._fields.keys();
12!
135
    let iterator: IterableIterator<[string, ApiField]> | undefined =
12✔
136
      this.fieldEntries(scope);
12✔
137
    let r: IteratorResult<[string, ApiField]>;
12✔
138
    return {
12✔
139
      next() {
12✔
140
        if (!iterator) return { done: true, value: undefined };
34!
141
        r = iterator!.next();
34✔
142
        return { done: r.done, value: r.value?.[0] };
34✔
143
      },
34✔
144
      return(value?: string) {
12✔
145
        iterator = undefined;
×
146
        return { done: true, value };
×
147
      },
×
148
      [Symbol.iterator]() {
12✔
149
        return this;
12✔
150
      },
12✔
151
    };
12✔
152
  }
12✔
153

1✔
154
  /**
1✔
155
   *
1✔
156
   */
1✔
157
  findField(nameOrPath: string, scope?: string | '*'): ApiField | undefined {
1✔
158
    if (nameOrPath.includes('.')) {
604✔
159
      const fieldPath = this.parseFieldPath(nameOrPath, { scope });
3✔
160
      if (fieldPath.length === 0)
3✔
161
        throw new Error(
3!
162
          `Field "${nameOrPath}" does not exist in scope "${scope}"`,
✔
163
        );
2✔
164
      const lastItem = fieldPath.pop();
2✔
165
      return lastItem?.field;
2✔
166
    }
3✔
167
    const field = this._fields.get(nameOrPath);
601✔
168
    if (field && field.inScope(scope)) return field.forScope(scope);
604✔
169
  }
604✔
170

1✔
171
  /**
1✔
172
   *
1✔
173
   */
1✔
174
  getField(nameOrPath: string, scope?: string): ApiField {
1✔
175
    const field = this.findField(nameOrPath, '*');
235✔
176
    if (field && !field.inScope(scope))
235✔
177
      throw new Error(
235!
178
        `Field "${nameOrPath}" does not exist in scope "${scope || 'null'}"`,
✔
179
      );
234✔
180
    if (!field) {
235✔
181
      throw new Error(`Field (${nameOrPath}) does not exist`);
1✔
182
    }
1✔
183
    return field.forScope(scope);
233✔
184
  }
233✔
185

1✔
186
  /**
1✔
187
   *
1✔
188
   */
1✔
189
  parseFieldPath(
1✔
190
    fieldPath: string,
206✔
191
    options?: {
206✔
192
      allowSigns?: 'first' | 'each';
206✔
193
      scope?: string | '*';
206✔
194
    },
206✔
195
  ): ComplexType.ParsedFieldPath[] {
206✔
196
    let dataType: DataType | undefined = this;
206✔
197
    let field: ApiField | undefined;
206✔
198
    const arr = fieldPath.split('.');
206✔
199
    const len = arr.length;
206✔
200
    const out: ComplexType.ParsedFieldPath[] = [];
206✔
201
    const objectType = this.owner.node.getDataType('object');
206✔
202
    const allowSigns = options?.allowSigns;
206✔
203
    const getStrPath = () => out.map(x => x.fieldName).join('.');
206✔
204

206✔
205
    for (let i = 0; i < len; i++) {
206✔
206
      const item: ComplexType.ParsedFieldPath = {
214✔
207
        fieldName: arr[i],
214✔
208
        dataType: objectType,
214✔
209
      };
214✔
210
      out.push(item);
214✔
211

214✔
212
      const m = FIELD_PATH_PATTERN.exec(arr[i]);
214✔
213
      if (!m) throw new TypeError(`Invalid field name (${getStrPath()})`);
214!
214
      if (m[1]) {
214✔
215
        if ((i === 0 && allowSigns === 'first') || allowSigns === 'each')
46✔
216
          item.sign = m[1] as any;
46✔
217
        item.fieldName = m[2];
46✔
218
      }
46✔
219

214✔
220
      if (dataType) {
214✔
221
        if (dataType instanceof ComplexTypeBase) {
214✔
222
          field = dataType.findField(item.fieldName, options?.scope);
213✔
223
          if (field) {
213✔
224
            item.fieldName = field.name;
211✔
225
            item.field = field;
211✔
226
            item.dataType = field.type;
211✔
227
            dataType = field.type;
211✔
228
            continue;
211✔
229
          }
211✔
230
          if (dataType.additionalFields?.[0] === true) {
213!
231
            item.additionalField = true;
×
232
            item.dataType = objectType;
×
233
            dataType = undefined;
×
234
            continue;
×
235
          }
✔
236
          if (
2✔
237
            dataType.additionalFields?.[0] === 'type' &&
213!
238
            dataType.additionalFields?.[1] instanceof DataType
×
239
          ) {
213!
240
            item.additionalField = true;
×
241
            item.dataType = dataType.additionalFields[1];
×
242
            dataType = dataType.additionalFields[1];
×
243
            continue;
×
244
          }
✔
245
          throw new Error(
2✔
246
            `Unknown field (${out.map(x => x.fieldName).join('.')})`,
2✔
247
          );
2✔
248
        }
2✔
249
        throw new TypeError(
1✔
250
          `"${out.map(x => x.fieldName).join('.')}" field is not a complex type and has no child fields`,
1✔
251
        );
1✔
252
      }
1!
253
      item.additionalField = true;
×
254
      item.dataType = objectType;
×
255
    }
✔
256
    return out;
211✔
257
  }
211✔
258

1✔
259
  /**
1✔
260
   *
1✔
261
   */
1✔
262
  normalizeFieldPath(
1✔
263
    fieldPath: string,
203✔
264
    options?: {
203✔
265
      allowSigns?: 'first' | 'each';
203✔
266
      scope?: string;
203✔
267
    },
203✔
268
  ): string {
203✔
269
    return this.parseFieldPath(fieldPath, options)
203✔
270
      .map(x => (x.sign || '') + x.fieldName)
203✔
271
      .join('.');
203✔
272
  }
203✔
273

1✔
274
  /**
1✔
275
   *
1✔
276
   */
1✔
277
  generateCodec(
1✔
278
    codec: 'encode' | 'decode',
551✔
279
    options?: DataType.GenerateCodecOptions,
551✔
280
  ): Validator {
551✔
281
    const context: GenerateCodecContext = (options as any)?.cache
551!
282
      ? (options as GenerateCodecContext)
×
283
      : {
551✔
284
          ...options,
551✔
285
          projection: Array.isArray(options?.projection)
551✔
286
            ? parseFieldsProjection(options.projection)
43✔
287
            : options?.projection,
508✔
288
          currentPath: '',
551✔
289
        };
551✔
290

551✔
291
    const schema = this._generateSchema(codec, context);
551✔
292

551✔
293
    let additionalFields: any;
551✔
294
    if (this.additionalFields instanceof DataType) {
551!
295
      additionalFields = this.additionalFields.generateCodec(codec, options);
×
296
    } else if (typeof this.additionalFields === 'boolean')
551✔
297
      additionalFields = this.additionalFields;
551✔
298
    else if (Array.isArray(this.additionalFields)) {
446!
299
      if (this.additionalFields.length < 2) additionalFields = 'error';
×
300
      else {
×
301
        const message = additionalFields[1] as string;
×
302
        additionalFields = validator((input, ctx, _this) =>
×
303
          ctx.fail(_this, message, input),
×
304
        );
×
305
      }
×
306
    }
×
307

551✔
308
    const fn = vg.isObject(schema, {
551✔
309
      ctor: this.name === 'object' ? Object : this.ctor,
551✔
310
      additionalFields,
551✔
311
      name: this.name,
551✔
312
      coerce: true,
551✔
313
      caseInSensitive: options?.caseInSensitive,
551✔
314
      onFail: options?.onFail,
551✔
315
    });
551✔
316
    if (context.level === 0 && context.forwardCallbacks?.size) {
551!
317
      for (const cb of context.forwardCallbacks) {
×
318
        cb();
×
319
      }
×
320
    }
×
321
    return fn;
551✔
322
  }
551✔
323

1✔
324
  protected _generateSchema(
1✔
325
    codec: 'encode' | 'decode',
560✔
326
    context: GenerateCodecContext,
560✔
327
  ): vg.isObject.Schema {
560✔
328
    context.fieldCache = context.fieldCache || new Map();
560✔
329
    context.level = context.level || 0;
560✔
330
    context.forwardCallbacks = context.forwardCallbacks || new Set();
560✔
331
    const schema: vg.isObject.Schema = {};
560✔
332
    const { currentPath, projection } = context;
560✔
333
    const pickList = !!(
560✔
334
      projection && Object.values(projection).find(p => !p.sign)
560✔
335
    );
560✔
336
    // Process fields
560✔
337
    let fieldName: string;
560✔
338
    for (const field of this.fields('*')) {
560✔
339
      if (
4,731✔
340
        /* Ignore field if required scope(s) do not match field scopes */
4,731✔
341
        !field.inScope(context.scope) ||
4,731✔
342
        (!(context.keepKeyFields && this.keyField) &&
4,673✔
343
          /* Ignore field if readonly and ignoreReadonlyFields option true */
3,664✔
344
          ((context.ignoreReadonlyFields && field.readonly) ||
3,664✔
345
            /* Ignore field if writeonly and ignoreWriteonlyFields option true */
3,535✔
346
            (context.ignoreWriteonlyFields && field.writeonly)))
3,664✔
347
      ) {
4,731✔
348
        schema[field.name] = vg.isUndefined({ coerce: true });
187✔
349
        continue;
187✔
350
      }
187✔
351
      fieldName = field.name;
4,544✔
352
      let p: any;
4,544✔
353
      if (projection !== '*') {
4,731✔
354
        p = projection?.[fieldName.toLowerCase()];
826✔
355
        if (
826✔
356
          /* Ignore if field is omitted */
826✔
357
          p?.sign === '-' ||
826✔
358
          /* Ignore if default fields ignored and field is not in projection */
801✔
359
          (pickList && !p) ||
801✔
360
          /* Ignore if default fields enabled and fields is exclusive */
598✔
361
          (!pickList && field.exclusive && !p)
598✔
362
        ) {
826✔
363
          schema[field.name] = vg.isUndefined({ coerce: true });
292✔
364
          continue;
292✔
365
        }
292✔
366
      }
826✔
367
      const subProjection =
4,252✔
368
        typeof projection === 'object'
4,252✔
369
          ? projection[fieldName]?.projection || '*'
355✔
370
          : projection;
3,897✔
371
      let cacheItem = context.fieldCache.get(field);
4,731✔
372
      const cacheKey =
4,731✔
373
        typeof subProjection === 'string'
4,731✔
374
          ? subProjection
4,073✔
375
          : hashObject(subProjection || {});
179✔
376
      if (!cacheItem) {
4,731✔
377
        cacheItem = {};
4,252✔
378
        context.fieldCache.set(field, cacheItem);
4,252✔
379
      }
4,252✔
380
      let fn = cacheItem[cacheKey];
4,252✔
381
      /* If in progress (circular) */
4,252✔
382
      if (fn === null) {
4,731!
383
        // Temporary set any
×
384
        fn = vg.isAny();
×
385
        context.forwardCallbacks.add(() => {
×
386
          fn = cacheItem[cacheKey];
×
387
          schema[fieldName] =
×
388
            context.partial || !field.required
×
389
              ? context.allowNullOptionals
×
390
                ? vg.nullable(fn!)
×
391
                : vg.optional(fn!)
×
392
              : vg.required(fn!);
×
393
        });
×
394
      } else if (!fn) {
4,731✔
395
        const defaultGenerator = () => {
4,252✔
396
          cacheItem[cacheKey] = null;
4,252✔
397
          const xfn = field.generateCodec(codec, {
4,252✔
398
            ...context,
4,252✔
399
            partial: context.partial === 'deep' ? context.partial : undefined,
4,252✔
400
            projection: subProjection,
4,252✔
401
            currentPath: currentPath + (currentPath ? '.' : '') + fieldName,
4,252!
402
            level: context.level! + 1,
4,252✔
403
          } as GenerateCodecContext);
4,252✔
404
          cacheItem[cacheKey] = xfn;
4,252✔
405
          return xfn;
4,252✔
406
        };
4,252✔
407
        if (context.fieldHook)
4,252✔
408
          fn = context.fieldHook(field, context.currentPath, defaultGenerator);
4,252✔
409
        else fn = defaultGenerator();
3,980✔
410
      }
4,252✔
411
      schema[fieldName] =
4,252✔
412
        context.partial || !(field.required || fn.id === 'required')
4,731✔
413
          ? context.allowNullOptionals
4,239✔
414
            ? vg.nullable(fn)
1,276✔
415
            : vg.optional(fn)
2,963✔
416
          : fn.id === 'required'
13✔
417
            ? fn
13!
418
            : vg.required(fn);
×
419
    }
4,731✔
420
    if (context.allowPatchOperators) {
560✔
421
      schema._$pull = vg.optional(vg.isAny());
130✔
422
      schema._$push = vg.optional(vg.isAny());
130✔
423
    }
130✔
424
    return schema;
560✔
425
  }
560✔
426
}
1✔
427

1✔
428
ComplexTypeBase.prototype = ComplexTypeBaseClass.prototype;
1✔
429

1✔
430
type GenerateCodecContext = StrictOmit<
1✔
431
  DataType.GenerateCodecOptions,
1✔
432
  'projection'
1✔
433
> & {
1✔
434
  currentPath: string;
1✔
435
  projection?: FieldsProjection | '*';
1✔
436
  level?: number;
1✔
437
  fieldCache?: Map<ApiField, Record<string, Validator | null>>;
1✔
438
  forwardCallbacks?: Set<Function>;
1✔
439
};
1✔
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc