# Types
This page contains the documentation of all other types and classes than Model and QueryBuilder. There are two types of items on this page:
type
: A type is just a POJO (Plain Old Javascript Object) with a set of properties.class
: A class is a JavaScript class with properties and methods.
# type
RelationMapping
Property | Type | Description |
---|---|---|
relation | function | The relation type. One of Model.BelongsToOneRelation , Model.HasOneRelation , Model.HasManyRelation , Model.ManyToManyRelation and Model.HasOneThroughRelation . |
modelClass | Model string | Constructor of the related model class, an absolute path to a module that exports one or a path relative to modelPaths that exports a model class. |
join | RelationJoin | Describes how the models are related to each other. See RelationJoin. |
modify | function(QueryBuilder) string object | Optional modifier for the relation query. If specified as a function, it will be called each time before fetching the relation. If specified as a string, modifier with specified name will be applied each time when fetching the relation. If specified as an object, it will be used as an additional query parameter - e. g. passing {name: 'Jenny'} would additionally narrow fetched rows to the ones with the name 'Jenny'. |
filter | function(QueryBuilder) string object | Alias for modify. |
beforeInsert | function(Model, QueryContext) | Optional insert hook that is called for each inserted model instance. This function can be async. |
# type
RelationJoin
Property | Type | Description |
---|---|---|
from | string ReferenceBuilder Array | The relation column in the owner table. Must be given with the table name. For example persons.id . Composite key can be specified using an array of columns e.g. ['persons.a', 'persons.b'] . Note that neither this nor to need to be foreign keys or primary keys. You can join any column to any column. You can even join nested json fields using the ref helper. |
to | string ReferenceBuilder Array | The relation column in the related table. Must be given with the table name. For example movies.id . Composite key can be specified using an array of columns e.g. ['movies.a', 'movies.b'] . Note that neither this nor from need to be foreign keys or primary keys. You can join any column to any column. You can even join nested json fields using the ref helper. |
through | RelationThrough | Describes the join table if the models are related through one. |
# type
RelationThrough
Property | Type | Description |
---|---|---|
from | string ReferenceBuilder Array | The column that is joined to from property of the RelationJoin . For example Person_movies.actorId where Person_movies is the join table. Composite key can be specified using an array of columns e.g. ['persons_movies.a', 'persons_movies.b'] . You can join nested json fields using the ref helper. |
to | string ReferenceBuilder Array | The column that is joined to to property of the RelationJoin . For example Person_movies.movieId where Person_movies is the join table. Composite key can be specified using an array of columns e.g. ['persons_movies.a', 'persons_movies.b'] . You can join nested json fields using the ref helper. |
modelClass | string ModelClass | If you have a model class for the join table, you should specify it here. This is optional so you don't need to create a model class if you don't want to. |
extra | string string[] Object | Join table columns listed here are automatically joined to the related objects when they are fetched and automatically written to the join table instead of the related table on insert and update. The values can be aliased by providing an object {propertyName: 'columnName', otherPropertyName: 'otherColumnName'} instead of array . |
beforeInsert | function(Model, QueryContext) | Optional insert hook that is called for each inserted join table model instance. This function can be async. |
# type
ModelOptions
Property | Type | Description |
---|---|---|
patch | boolean | If true the json is treated as a patch and the required field of the json schema is ignored in the validation. This allows us to create models with a subset of required properties for patch operations. |
skipValidation | boolean | If true the json schema validation is skipped |
old | object | The old values for methods like $beforeUpdate and $beforeValidate . |
# type
CloneOptions
Property | Type | Description |
---|---|---|
shallow | boolean | If true, relations are ignored |
# type
ToJsonOptions
Property | Type | Description |
---|---|---|
shallow | boolean | If true, relations are ignored. Default is false. |
virtuals | boolean string[] | If false, virtual attributes are omitted from the output. Default is true. You can also pass an array of property names and only those virtual properties get picked. You can even pass in property/function names that are not included in the static virtualAttributes array. |
# type
GraphOptions
Property | Type | Description |
---|---|---|
minimize | boolean | If true the aliases of the joined tables and columns created by withGraphJoined are minimized. This is sometimes needed because of identifier length limitations of some database engines. ExpressWeb JS throws an exception when a query exceeds the length limit. You need to use this only in those cases. |
separator | string | Separator between relations in nested withGraphJoined query. Defaults to : . Dot (. ) cannot be used at the moment because of the way knex parses the identifiers. |
aliases | Object | Aliases for relations in a withGraphJoined query. Defaults to an empty object. |
joinOperation | string | Which join type to use ['leftJoin', 'innerJoin', 'rightJoin', ...] or any other knex join method name. Defaults to leftJoin . |
maxBatchSize | integer | For how many parents should a relation be fetched using a single query at a time. If you set this to 1 then a separate query is used for each parent to fetch a relation. For example if you want to fetch pets for 5 persons, you get five queries (one for each person). Setting this to 1 will allow you to use stuff like limit and aggregate functions in modifyGraph and other graph modifiers. This can be used to replace the naiveEager ExpressWeb JS 1.x had. |
# type
UpsertGraphOptions
Property | Type | Description |
---|---|---|
relate | boolean string[] | If true, relations are related instead of inserted. Relate functionality can be enabled for a subset of relations of the graph by providing a list of relation expressions. See the examples here. |
unrelate | boolean string[] | If true, relations are unrelated instead of deleted. Unrelate functionality can be enabled for a subset of relations of the graph by providing a list of relation expressions. See the examples here. |
insertMissing | boolean string[] | If true, models that have identifiers and are not found in the database, are inserted. By default this is false and an error is thrown. This functionality can be enabled for a subset of relations of the graph by providing a list of relation expressions. See the examples here. |
update | boolean string[] | If true, update operations are performed instead of patch when altering existing models, affecting the way the data is validated. With update operations, all required fields need to be present in the data provided. This functionality can be enabled for a subset of relations of the graph by providing a list of relation expressions. See the examples here. |
noInsert | boolean string[] | If true, no inserts are performed. Inserts can be disabled for a subset of relations of the graph by providing a list of relation expressions. See the examples here. |
noUpdate | boolean string[] | If true, no updates are performed. Updates can be disabled for a subset of relations of the graph by providing a list of relation expressions. See the examples here. |
noDelete | boolean string[] | If true, no deletes are performed. Deletes can be disabled for a subset of relations of the graph by providing a list of relation expressions. See the examples here. |
noRelate | boolean string[] | If true, no relates are performed. Relate operations can be disabled for a subset of relations of the graph by providing a list of relation expressions. See the examples here. |
noUnrelate | boolean string[] | If true, no unrelate operations are performed. Unrelate operations can be disabled for a subset of relations of the graph by providing a list of relation expressions. See the examples here. |
allowRefs | boolean | This needs to be true if you want to use #ref in your graphs. See this section for #ref usage examples. |
# type
InsertGraphOptions
Property | Type | Description |
---|---|---|
relate | boolean string[] | If true, models with an id are related instead of inserted. Relate functionality can be enabled for a subset of relations of the graph by providing a list of relation expressions. See the examples here. |
allowRefs | boolean | This needs to be true if you want to use #ref in your graphs. See this section for #ref usage examples. |
# type
FetchGraphOptions
Property | Type | Description |
---|---|---|
transaction | knex Transaction | Optional transaction or knex instance for the query. This can be used to specify a transaction or even a different database. |
skipFetched | boolean | If true, only fetch relations that don't yet exist in the object. |
# type
TableMetadataFetchOptions
Property | Type | Description |
---|---|---|
table | string | A custom table name. If not given, Model.tableName is used. |
knex | knex Transaction | A knex instance or a transaction |
# type
TableMetadataOptions
Property | Type | Description |
---|---|---|
table | string | A custom table name. If not given, Model.tableName is used. |
# type
TableMetadata
Property | Type | Description |
---|---|---|
columns | string[] | Names of all the columns in a table. |
# type
StaticHookArguments
Property | Type | Description |
---|---|---|
items | Model[] | Items for which the query was started. For example in case of an instance query person.$query() or person.$relatedQuery('pets') items would equal [person] . In case of Person.relatedQuery('pets').for([matt, jennifer]) items would equal [matt, jennifer] . In many cases like Person.query() or Person.query().findById(1) this array is empty. It's only populated when the query has been explicitly started for a set of model instances. |
inputItems | Model[] | Items that were passed as an input for the query. For example in case of Person.query().insert(person) or Person.query().patch(person) inputItems would equal [person] . |
asFindQuery | () => QueryBuilder | A function that returns a query builder that can be used to fetch the items that were/would get affected by the query being executed. Modifying this query builder doesn't affect the query being executed. For example calling await asFindQuery().select('id') in a beforeDelete hook would get you the identifiers of all the items that will get deleted by the query. This query is automatically executed inside any existing transaction. This query builder always returns an array even if the query being executed would return an object, a number or something else. |
transaction | knex Transaction | If the query being executed has a transaction, this property will contain it. Otherwise this holds the knex instance installed for the query. Either way, this can and should be passed to any queries executed in the static hooks. |
context | object | The context of the query. See context. |
relation | Relation | If the query is for a relation, this property holds the Relation object. For example when you call person.$relatedQuery('pets) or Person.relatedQuery('movies') the relation will be a relation object for pets and movies relation of Person respectively. |
cancelQuery | function(any) | Cancels the query being executed. You can pass an arugment for the function and that value will be the result of the query. |
# type
FieldExpression
Field expressions are strings that allow you to refer to JSONB fields inside columns.
Syntax: <column reference>[:<json field reference>]
e.g. persons.jsonColumnName:details.names[1]
would refer to value 'Second'
in column persons.jsonColumnName
which has
{ details: { names: ['First', 'Second', 'Last'] } }
object stored in it.
First part <column reference>
is compatible with column references used in knex e.g. MyFancyTable.tributeToThBestColumnNameEver
.
Second part describes a path to an attribute inside the referred column. It is optional and it always starts with colon which follows directly with first path element. e.g. Table.jsonObjectColumnName:jsonFieldName
or Table.jsonArrayColumn:[321]
.
Syntax supports [<key or index>]
and .<key or index>
flavors of reference to json keys / array indexes:
e.g. both Table.myColumn:[1][3]
and Table.myColumn:1.3
would access correctly both of the following objects [null, [null,null,null, "I was accessed"]]
and { "1": { "3" : "I was accessed" } }
Caveats when using special characters in keys:
objectColumn.key
This is the most common syntax, good if you are not using dots or square brackets[]
in your json object key name.- Keys containing dots
objectColumn:[keywith.dots]
Column{ "keywith.dots" : "I was referred" }
- Keys containing square brackets
column['[]']
{ "[]" : "This is getting ridiculous..." }
- Keys containing square brackets and quotes
objectColumn:['Double."Quote".[]']
andobjectColumn:["Sinlge.'Quote'.[]"]
Column{ "Double.\"Quote\".[]" : "I was referred", "Sinlge.'Quote'.[]" : "Mee too!" }
- Keys containing dots, square brackets, single quotes and double quotes in one json key is not currently supported
There are some special methods that accept FieldExpression
strings directly, like whereJsonSupersetOf but you can use FieldExpressions
anywhere with ref. Here's an example:
const { ref } = require("objection"); await Person.query() .select([ "id", ref("persons.jsonColumn:details.name").castText().as("name"), ref("persons.jsonColumn:details.age").castInt().as("age"), ]) .join( "someTable", ref("persons.jsonColumn:details.name").castText(), "=", ref("someTable.name") ) .where("age", ">", ref("someTable.ageLimit"));
Copied!
In the above example, we assume persons
table has a column named jsonColumn
of type jsonb
(only works on postgres).
# type
RelationExpression
Relation expression is a simple DSL for expressing relation trees.
These strings are all valid relation expressions:
children
children.movies
[children, pets]
[children.movies, pets]
[children.[movies, pets], pets]
[children.[movies.actors.[children, pets], pets], pets]
[children as kids, pets(filterDogs) as dogs]
There are two tokens that have special meaning: *
and ^
. *
means "all relations recursively" and ^
means "this relation recursively".
For example children.*
means "relation children
and all its relations, and all their relations and ...".
WARNING
The * token must be used with caution or you will end up fetching your entire database.
Expression parent.^
is equivalent to parent.parent.parent.parent...
up to the point a relation no longer has results for the parent
relation. The recursion can be limited to certain depth by giving the depth after the ^
character. For example parent.^3
is equal to parent.parent.parent
.
Relations can be aliased using the as
keyword.
For example the expression children.[movies.actors.[pets, children], pets]
represents a tree:
children (Person) | ----------------- | | movies pets (Movie) (Animal) | actors (Person) | ----------- | | pets children (Animal) (Person)
Copied!
The model classes are shown in parenthesis. When given to withGraphFetched
method, this expression would fetch all relations as shown in the tree above:
const people = await Person.query().withGraphFetched( "children.[movies.actors.[pets, children], pets]" ); // All persons have the given relation tree fetched. console.log(people[0].children[0].movies[0].actors[0].pets[0].name);
Copied!
Relation expressions can have arguments. Arguments are used to refer to modifier functions (either global or local. Arguments are listed in parenthesis after the relation names like this:
Person.query().withGraphFetched( `children(arg1, arg2).[movies.actors(arg3), pets]` );
Copied!
You can spread relation expressions to multiple lines and add whitespace:
Person.query().withGraphFetched(`[ children.[ pets, movies.actors.[ pets, children ] ] ]`);
Copied!
Relation expressions can be aliased using as
keyword:
Person.query().withGraphFetched(`[ children as kids.[ pets(filterDogs) as dogs, pets(filterCats) as cats, movies.actors.[ pets, children as kids ] ] ]`);
Copied!
# RelationExpression object notation
In addition to the string expressions, a more verbose object notation can also be used.
The string expression in the comment is equivalent to the object expression below it:
// `children` { children: true; }
Copied!
// `children.movies` { children: { movies: true; } }
Copied!
// `[children, pets]` { children: true; pets: true; }
Copied!
// `[children.[movies, pets], pets]` { children: { movies: true, pets: true } pets: true }
Copied!
// `parent.^` { parent: { $recursive: true; } }
Copied!
// `parent.^5` { parent: { $recursive: 5; } }
Copied!
// `parent.*` { parent: { $allRecursive: true; } }
Copied!
// `[children as kids, pets(filterDogs) as dogs]` { kids: { $relation: 'children' }, dogs: { $relation: 'pets', $modify: ['filterDogs'] } }
Copied!
# type
TransactionObject
This is nothing more than a knex transaction object. It can be used as a knex query builder, it can be passed to ExpressWeb JS queries and models can be bound to it.
# Instance Methods
# commit()
const promise = trx.commit();
Copied!
Call this method to commit the transaction. This only needs to be called if you use transaction.start()
method.
# rollback()
const promise = trx.rollback(error);
Copied!
Call this method to rollback the transaction. This only needs to be called if you use transaction.start()
method. You need to pass the error to the method as the only argument.
# class
ValidationError
const { ValidationError } = require("objection"); throw new ValidationError({ type, message, data });
Copied!
For each key
, a list of errors is given. Each error contains the default message
(as returned by the validator), an optional keyword
string to identify the validation rule which didn't pass and a param
object which optionally contains more details about the context of the validation error.
If type
is anything else but "ModelValidation"
, data
can be any object that describes the error.
Error of this class is thrown by default if validation of any input fails. By input we mean any data that can come from the outside world, like model instances (or POJOs), relation expressions object graphs etc.
You can replace this error by overriding Model.createValidationError() method.
Property | Type | Description |
---|---|---|
statusCode | number | HTTP status code for interop with express error handlers and other libraries that search for status code from errors. |
type | string | One of "ModelValidation", "RelationExpression", "UnallowedRelation" and "InvalidGraph". This can be any string for your own custom errors. The listed values are used internally by ExpressWeb JS. |
data | object | The content of this property is documented below for "ModelValidation" errors. For other types, this can be any data. |
If type
is "ModelValidation"
then data
object should follow this pattern:
{ key1: [{ message: '...', keyword: 'required', params: null }, { message: '...', keyword: '...', params: { ... } }, ...], key2: [{ message: '...', keyword: 'minLength', params: { limit: 1, ... } }, ...], ... }
Copied!
# class
NotFoundError
const { NotFoundError } = require("objection"); throw new NotFoundError(data);
Copied!
Error of this class is thrown by default by throwIfNotFound()
You can replace this error by overriding Model.createNotFoundError() method.
# class
Relation
Relation
is a parsed and normalized instance of a RelationMapping. Relation
s can be accessed using the getRelations method.
Relation
holds a RelationProperty instance for each property that is used to create the relationship between two tables.
Relation
is actually a base class for all relation types BelongsToOneRelation
, HasManyRelation
etc. You can use instanceof
to determine the type of the relations (see the example on the right). Note that HasOneRelation
is a subclass of HasManyRelation
and HasOneThroughRelation
is a subclass of ManyToManyRelation
. Arrange your instanceof
checks accordingly.
Property | Type | Description |
---|---|---|
name | string | Name of the relation. For example pets or children . |
ownerModelClass | function | The model class that has defined the relation. |
relatedModelClass | function | The model class of the related objects. |
ownerProp | RelationProperty | The relation property in the ownerModelClass . |
relatedProp | RelationProperty | The relation property in the relatedModelClass . |
joinModelClass | function | The model class representing the join table. This class is automatically generated by ExpressWeb JS if none is provided in the join.through.modelClass setting of the relation mapping, see RelationThrough. |
joinTable | string | The name of the join table (only for ManyToMany and HasOneThrough relations). |
joinTableOwnerProp | RelationProperty | The join table property pointing to ownerProp (only for ManyToMany and HasOneThrough relations). |
joinTableRelatedProp | RelationProperty | The join table property pointing to relatedProp (only for ManyToMany and HasOneThrough relations). |
Note that Relation
instances are actually instances of the relation classes used in relationMappings
. For example:
class Person extends Model { static get relationMappings() { return { pets: { relation: Model.HasManyRelation, modelClass: Animal, join: { from: "persons.id", to: "animals.ownerId", }, }, }; } } const relations = Person.getRelations(); console.log(relations.pets instanceof Model.HasManyRelation); // --> true console.log(relations.pets.name); // --> pets console.log(relations.pets.ownerProp.cols); // --> ['id'] console.log(relations.pets.relatedProp.cols); // --> ['ownerId']
Copied!
# class
RelationProperty
Represents a property that is used to create relationship between two tables. A single RelationProperty
instance can represent
composite key. In addition to a table column, A RelationProperty
can represent a nested field inside a column (for example a jsonb column).
# Properties
Property | Type | Description |
---|---|---|
size | number | The number of columns. In case of composite key, this is greater than one. |
modelClass | function | The model class that owns the property. |
props | string[] | The column names converted to "external" format. For example if modelClass defines a snake_case to camelCase conversion, these names are in camelCase. Note that a RelationProperty may actually point to a sub-properties of the columns in case they are of json or some other non-scalar type. This array always contains only the converted column names. Use getProp(obj, idx) method to get the actual value from an object. |
cols | string[] | The column names in the database format. For example if modelClass defines a snake_case to camelCase conversion, these names are in snake_case. Note that a RelationProperty may actually point to a sub-properties of the columns in case they are of json or some other non-scalar type. This array always contains only the column names. |
# Methods
# getProp()
const value = property.getProp(obj, index);
Copied!
Gets this property's index:th value from an object. For example if the property represents a composite key [a, b.d.e, c]
and obj is {a: 1, b: {d: {e: 2}}, c: 3}
then getProp(obj, 1)
would return 2
.
# setProp()
property.setProp(obj, index, value);
Copied!
Sets this property's index:th value in an object. For example if the property represents a composite key [a, b.d.e, c]
and obj is {a: 1, b: {d: {e: 2}}, c: 3}
then setProp(obj, 1, 'foo')
would mutate obj
into {a: 1, b: {d: {e: 'foo'}}, c: 3}
.
# fullCol()
const col = property.fullCol(builder, index);
Copied!
Returns the property's index:th column name with the correct table reference. Something like "Table.column"
.
The first argument must be an ExpressWeb JS QueryBuilder instance.
# ref()
const ref = property.ref(builder, index);
Copied!
Allows you to do things like this:
const builder = Person.query(); const ref = property.ref(builder, 0); builder.where(ref, ">", 10);
Copied!
Returns a ReferenceBuilder instance that points to the index:th column.
# patch()
property.patch(patchObj, index, value);
Copied!
Allows you to do things like this:
const builder = Person.query(); const patch = {}; property.patch(patch, 0, "foo"); builder.patch(patch);
Copied!
Appends an update operation for the index:th column into patchObj
object.
# class
ReferenceBuilder
An instance of this is returned from the ref helper function.
# Instance Methods
# castText()
Cast reference to sql type text
.
# castInt()
Cast reference to sql type integer
.
# castBigInt()
Cast reference to sql type bigint
.
# castFloat()
Cast reference to sql type float
.
# castDecimal()
Cast reference to sql type decimal
.
# castReal()
Cast reference to sql type real
.
# castBool()
Cast reference to sql type boolean
.
# castType()
Give custom type to which referenced value is cast to.
DEPRECATED: Use castTo
instead. castType
Will be removed in 2.0.
.castType('mytype') --> CAST(?? as mytype)
# castTo()
Give custom type to which referenced value is cast to.
.castTo('mytype') --> CAST(?? as mytype)
# castJson()
In addition to other casts wrap reference to_jsonb() function so that final value reference will be json type.
# as()
Gives an alias for the reference .select(ref('age').as('yougness'))
# from()
Specifies that table of the reference.
# class
ValueBuilder
An instance of this is returned from the val helper function. If an object is given as a value, it is cast to json by default.
# Instance Methods
# castText()
Cast to sql type text
.
# castInt()
Cast to sql type integer
.
# castBigInt()
Cast to sql type bigint
.
# castFloat()
Cast to sql type float
.
# castDecimal()
Cast to sql type decimal
.
# castReal()
Cast to sql type real
.
# castBool()
Cast to sql type boolean
.
# castTo()
Give custom type to which referenced value is cast to.
.castTo('mytype') --> CAST(?? as mytype)
# castJson()
Converts the value to json (jsonb in case of postgresql). The default cast type for object values.
# asArray()
Converts the value to an array.
val([1, 2, 3]).asArray() --> ARRAY[?, ?, ?]
Can be used in conjuction with castTo
.
val([1, 2, 3]).asArray().castTo('real[]') -> CAST(ARRAY[?, ?, ?] AS real[])
# as()
Gives an alias for the reference .select(ref('age').as('yougness'))
# class
RawBuilder
An instance of this is returned from the raw helper function.
# Instance Methods
# as()
Gives an alias for the raw expression .select(raw('concat(foo, bar)').as('fooBar'))
.
You should use this instead of inserting the alias to the SQL to give ExpressWeb JS more information about the query. Some edge cases, like using raw
in select
inside a withGraphJoined
modifier won't work unless you use this method.
# class
FunctionBuilder
An instance of this is returned from the fn helper function.
# Instance Methods
# as()
Gives an alias for the raw expression .select(fn('concat', 'foo', 'bar').as('fooBar'))
.
You should use this instead of inserting the alias to the SQL to ExpressWeb JS more information about the query. Some edge cases, like using fn
in select
inside a withGraphJoined
modifier won't work unless you use this method.
# class
Validator
const { Validator } = require("objection");
Copied!
Abstract class from which model validators must be inherited. See the example for explanation. Also check out the createValidator method.
# Examples
const { Validator } = require("objection"); class MyCustomValidator extends Validator { validate(args) { // The model instance. May be empty at this point. const model = args.model; // The properties to validate. After validation these values will // be merged into `model` by objection. const json = args.json; // `ModelOptions` object. If your custom validator sets default // values, you need to check the `opt.patch` boolean. If it is true // we are validating a patch object and the defaults should not be set. const opt = args.options; // A context object shared between the validation methods. A new // object is created for each validation operation. You can store // any data here. const ctx = args.ctx; // Do your validation here and throw any exception if the // validation fails. doSomeValidationAndThrowIfFails(json); // You need to return the (possibly modified) json. return json; } beforeValidate(args) { // Takes the same arguments as `validate`. Usually there is no need // to override this. return super.beforeValidate(args); } afterValidate(args) { // Takes the same arguments as `validate`. Usually there is no need // to override this. return super.afterValidate(args); } } const { Model } = require("objection"); // Override the `createValidator` method of a `Model` to use the // custom validator. class BaseModel extends Model { static createValidator() { return new MyCustomValidator(); } }
Copied!
# class
AjvValidator
const { AjvValidator } = require("objection");
Copied!
The default Ajv (opens new window) based json schema validator. You can override the createValidator method of Model like in the example to modify the validator.
# Examples
const { Model, AjvValidator } = require("objection"); class BaseModel extends Model { static createValidator() { return new AjvValidator({ onCreateAjv: (ajv) => { // Here you can modify the `Ajv` instance. }, options: { allErrors: true, validateSchema: false, ownProperties: true, v5: true, }, }); } }
Copied!