realm-object-server
Version:
919 lines • 40.1 kB
JavaScript
;
var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = (this && this.__metadata) || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
var __param = (this && this.__param) || function (paramIndex, decorator) {
return function (target, key) { decorator(target, key, paramIndex); }
};
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const apollo_server_express_1 = require("apollo-server-express");
const express = require("express");
const graphql_1 = require("graphql");
const graphql_subscriptions_1 = require("graphql-subscriptions");
const graphql_tools_1 = require("graphql-tools");
const LRU = require("lru-cache");
const moment = require("moment");
const pluralize = require("pluralize");
const realmUtil_1 = require("../shared/realmUtil");
const subscriptions_transport_ws_1 = require("subscriptions-transport-ws");
const timers_1 = require("timers");
const url = require("url");
const uuid_1 = require("uuid");
const Token_1 = require("../shared/Token");
const decorators_1 = require("../decorators");
const errors = require("../errors");
const AdminRealm_1 = require("../realms/AdminRealm");
const util_1 = require("../shared/util");
const ConfigurableServiceBase_1 = require("./ConfigurableServiceBase");
const RealmProblem_1 = require("../errors/RealmProblem");
let GraphQLService = class GraphQLService extends ConfigurableServiceBase_1.ConfigurableServiceBase {
constructor(config = {}) {
super(config);
this.schemaHandlers = {};
this.querySubscriptions = {};
this.linkingObjectsResolver = {
_linkingObjects: (parent, args) => {
let result = parent.linkingObjects(args.objectType, args.property);
if (args.query) {
result = result.filtered(args.query);
}
return this.getCollectionResponse(result, args);
},
};
this.subscriptionObjectNameRegex = /^(class_)?(.*?)(_matches)?$/gm;
}
setConfigCore(config) {
if (config.schemaCacheSettings !== "NoCache") {
this.schemaCache = new LRU({
max: (config.schemaCacheSettings && config.schemaCacheSettings.max) || 1000,
maxAge: config.schemaCacheSettings && config.schemaCacheSettings.maxAge,
});
}
this.disableAuthentication = config.disableAuthentication || false;
this.disableExplorer = config.disableExplorer || false;
this.realmCacheTTL = config.realmCacheMaxAge || 120000;
this.forceExplorerSSL = config.forceExplorerSSL;
this.includeCountInResponses = config.includeCountInResponses || false;
this.presentIntsAsFloatsInSchema = config.presentIntsAsFloatsInSchema || false;
this.collectionModelSuffix = config.collectionModelSuffix || "Collection";
this.namedSubscriptionModelName = config.namedSubscriptionModelName || "NamedSubscription";
this.inputModelSuffix = config.inputModelSuffix || "Input";
this.allRealmTypesName = config.allRealmTypesModelName || "AllRealmTypes";
this.updatePolicyModelName = config.updatePolicyModelName || "UpdatePolicy";
this.base64Type = new graphql_1.GraphQLScalarType({
name: config.base64ModelName || "Base64",
description: "A base64-encoded binary blob",
serialize(value) {
return Buffer.from(value).toString("base64");
},
parseValue(value) {
return Buffer.from(value, "base64");
},
parseLiteral(ast) {
if (ast.kind === "StringValue") {
return Buffer.from(ast.value, "base64");
}
throw new TypeError(`Expected StringValue literal, but got ${ast.kind}`);
},
});
this.dateType = new graphql_1.GraphQLScalarType({
name: config.dateModelName || "Date",
description: "An ISO 8601 represantation of a date value",
serialize(value) {
return value.toJSON();
},
parseValue(value) {
return new Date(value);
},
parseLiteral(ast) {
if (ast.kind === "StringValue") {
return new Date(ast.value);
}
throw new TypeError(`Expected StringValue literal, but got ${ast.kind}`);
},
});
}
startCore(server) {
return __awaiter(this, void 0, void 0, function* () {
this.pubsub = new graphql_subscriptions_1.PubSub();
const getOperationId = (socket, messageId) => {
return `${socket.id}_${messageId}`;
};
this.subscriptionServer = new subscriptions_transport_ws_1.SubscriptionServer({
execute: graphql_1.execute,
subscribe: graphql_1.subscribe,
onOperationComplete: (socket, messageId) => {
const opid = getOperationId(socket, messageId);
const details = this.querySubscriptions[opid];
if (details) {
details.results.removeAllListeners();
this.closeRealm(details.realm);
delete this.querySubscriptions[opid];
}
this.metrics.activeSubscriptions.dec({});
},
onOperation: (message, params, socket) => __awaiter(this, void 0, void 0, function* () {
if (!socket.realmPath) {
throw new graphql_1.GraphQLError('Missing "realmPath" from context. It is required for subscriptions.');
}
const context = params.context;
context.operationId = getOperationId(socket, message.id);
context.realm = yield this.openRealm(socket.realmPath, context.user);
params.schema = this.getSchema(socket.realmPath, context.realm);
this.metrics.activeSubscriptions.inc({});
return params;
}),
onConnect: (authPayload, socket) => __awaiter(this, void 0, void 0, function* () {
let accessToken;
let user;
if (!this.disableAuthentication) {
if (!authPayload || !authPayload.token) {
throw new errors.realm.MissingParameters("Missing 'connectionParams.token'.");
}
accessToken = this.server.tokenValidator.parse(authPayload.token);
user = yield this.authenticate(accessToken, socket.realmPath);
}
if (!socket.id) {
socket.id = uuid_1.v4();
this.metrics.openWebsockets.inc({});
}
return {
accessToken,
user,
};
}),
onDisconnect: (socket, context) => {
if (socket.id) {
this.metrics.openWebsockets.dec({});
}
}
}, {
noServer: true,
});
this.handler = apollo_server_express_1.graphqlExpress((req, res) => __awaiter(this, void 0, void 0, function* () {
this.metrics.totalRequests.inc({});
let realm;
res.once("finish", () => {
this.closeRealm(realm);
let contentLength = res["_contentLength"];
if (typeof contentLength === "undefined") {
const header = res.getHeader("content-length");
if (header !== null) {
contentLength = parseInt(header);
}
}
if (typeof contentLength === "number") {
this.metrics.responseSize.observe({}, contentLength);
}
});
const path = this.getPath(req);
realm = yield this.openRealm(path, req.user);
const schema = this.getSchema(path, realm);
const result = {
schema,
context: {
realm,
accessToken: req.authToken,
user: req.user,
},
};
return result;
}));
this.graphiql = apollo_server_express_1.graphiqlExpress((req) => {
const path = this.getPath(req);
let protocol;
switch (this.forceExplorerSSL) {
case true:
protocol = "wss";
break;
case false:
protocol = "ws";
break;
default:
protocol = req.protocol === "https" ? "wss" : "ws";
break;
}
const result = {
endpointURL: `/graphql/${encodeURIComponent(path)}`,
subscriptionsEndpoint: `${protocol}://${req.get("host")}/graphql/${encodeURIComponent(path)}`,
};
const token = req.get("authorization");
if (token) {
result.passHeader = `'Authorization': '${token}'`;
result.websocketConnectionParams = { token };
}
return result;
});
this.adminRealm = yield server.openRealm(AdminRealm_1.AdminRealm);
this.metrics = {
totalRequests: this.stats.counter({
name: "ros_graphql_requests_total",
help: "Counter for all GraphQL requests",
}),
responseSize: this.stats.histogram({
name: "ros_graphql_response_size_bytes",
help: "HTTP response body sizes of the GraphQL service",
buckets: [1, 25, 50, 100, 250, 500, 1024].map(v => v * 1024),
}),
activeSubscriptions: this.stats.gauge({
name: "ros_graphql_active_subscriptions",
help: "Number of active GraphQL subscriptions",
}),
openWebsockets: this.stats.gauge({
name: "ros_graphql_open_websockets",
help: "Number of open websockets to the GraphQL service",
}),
};
});
}
stopCore() {
return __awaiter(this, void 0, void 0, function* () {
if (this.adminRealm) {
this.adminRealm.close();
delete this.adminRealm;
}
this.subscriptionServer.close();
});
}
subscriptionHandler(req, socket, head) {
return __awaiter(this, void 0, void 0, function* () {
const wsServer = this.subscriptionServer.server;
const ws = yield new Promise((resolve) => wsServer.handleUpgrade(req, socket, head, resolve));
const path = url.parse(req.url).path.replace("/graphql/", "");
ws.realmPath = this.getPath(path);
wsServer.emit("connection", ws, req);
});
}
getExplore(req, res, next) {
return __awaiter(this, void 0, void 0, function* () {
if (this.disableExplorer) {
throw new errors.realm.AccessDenied();
}
yield this.authenticateRequest(req);
this.graphiql(req, res, next);
});
}
postExplore(req, res, next) {
return __awaiter(this, void 0, void 0, function* () {
if (this.disableExplorer) {
throw new errors.realm.AccessDenied();
}
yield this.authenticateRequest(req);
this.graphiql(req, res, next);
});
}
get(req, res, next) {
return __awaiter(this, void 0, void 0, function* () {
yield this.authenticateRequest(req);
this.handler(req, res, next);
});
}
post(req, res, next) {
return __awaiter(this, void 0, void 0, function* () {
yield this.authenticateRequest(req);
this.handler(req, res, next);
});
}
deleteSchema(req, res) {
return __awaiter(this, void 0, void 0, function* () {
yield this.authenticateRequest(req);
this.schemaCache.del(this.getPath(req));
res.status(204).send({});
});
}
getPath(reqOrPath) {
let path = typeof reqOrPath === "string" ? decodeURIComponent(reqOrPath) : reqOrPath.params["0"];
if (!path.startsWith("/")) {
path = `/${path}`;
}
return path;
}
authenticateRequest(req) {
return __awaiter(this, void 0, void 0, function* () {
req.user = yield this.authenticate(req.authToken, this.getPath(req));
});
}
authenticate(authToken, path) {
return __awaiter(this, void 0, void 0, function* () {
if (this.disableAuthentication) {
return undefined;
}
if (!authToken) {
throw new errors.realm.AccessDenied({ detail: "Authorization header is missing." });
}
const accessToken = authToken;
if (!this.server.tokenValidator.isAdminToken(authToken) && (!path || accessToken.path !== path)) {
throw new errors.realm.InvalidCredentials({ detail: "The access token doesn't grant access to the requested path." });
}
const partialInfo = util_1.extractPartialInfo(path);
if (!partialInfo.isPartial) {
return undefined;
}
if (!partialInfo.customIdentifier.startsWith(authToken.identity + "/")) {
throw new errors.realm.InvalidCredentials({
detail: "The identifier after /__partial/ in the route must match the user Id. Expected: "
+ `'/__partial/${authToken.identity}/*', but got '/__partial/${partialInfo.customIdentifier}'.`,
});
}
if (!this.adminRealm) {
throw new errors.realm.ServiceUnavailable();
}
const adminRealmUser = this.adminRealm.objectForPrimaryKey("User", authToken.identity);
const refreshToken = new Token_1.RefreshToken({
appId: authToken.appId,
identity: authToken.identity,
isAdmin: (adminRealmUser && adminRealmUser.isAdmin) || false,
expires: moment().add(1, "year").unix(),
});
const authService = yield this.server.discovery.waitForService("auth");
const user = realmUtil_1.Realm.Sync.User.deserialize({
identity: refreshToken.identity,
isAdmin: refreshToken.isAdmin,
refreshToken: refreshToken.sign(this.server.privateKey),
server: `http://${authService.address}:${authService.port}`,
});
return user;
});
}
validateAccess(context, access) {
if (this.disableAuthentication || this.server.tokenValidator.isAdminToken(context.accessToken)) {
return;
}
const token = context.accessToken;
if (!token || !token.access || token.access.indexOf(access) < 0) {
throw new errors.realm.InvalidCredentials({
title: `The current user doesn't have '${access}' access.`,
});
}
}
closeRealm(realm) {
if (!realm) {
return;
}
if (this.realmCacheTTL >= 0) {
timers_1.setTimeout(() => realm.close(), this.realmCacheTTL);
}
else {
realm.close();
}
}
validateRead(context) {
this.validateAccess(context, "download");
}
validateWrite(context) {
this.validateAccess(context, "upload");
}
getSchema(path, realm) {
if (this.schemaCache && this.schemaCache.has(path)) {
return this.schemaCache.get(path);
}
let schema = `
scalar ${this.base64Type.name}
scalar ${this.dateType.name}
enum ${this.updatePolicyModelName} {
NEVER
MODIFIED
ALL
}
`;
const types = new Array();
const queryResolver = {};
const mutationResolver = {};
const subscriptionResolver = {};
const extraResolvers = {};
const partialInfo = util_1.extractPartialInfo(path);
for (const obj of realm.schema) {
if (this.isReserved(obj.name)) {
continue;
}
const propertyInfo = this.getPropertySchema(obj);
if (!propertyInfo.propertySchema) {
continue;
}
types.push([obj.name, propertyInfo]);
}
for (const [type, propertyInfo] of types) {
schema += `type ${type} {
${propertyInfo.propertySchema}
_linkingObjects(objectType: String!, property: String!, query: String, skip: Int, take: Int): ${this.getCollectionType(this.allRealmTypesName)}
}\n\n`;
schema += `type ${type}${this.collectionModelSuffix} {
count: Int!
items: [${type}!]
}\n`;
schema += `input ${type}${this.inputModelSuffix} { \n${propertyInfo.inputPropertySchema}}\n\n`;
}
if (types.length === 0) {
throw new Error(`The schema for Realm at path ${path} is empty.`);
}
const allTypesUnion = types.map(([type, _]) => type).join(" | ");
const allTypesCollectionUnion = types.map(([type, _]) => `${type}${this.collectionModelSuffix}`).join(" | ");
schema += `union ${this.allRealmTypesName} = ${allTypesUnion}\n`;
schema += `union ${this.allRealmTypesName}${this.collectionModelSuffix} = ${allTypesCollectionUnion}\n`;
let query = "type Query {\n";
let mutation = "type Mutation {\n";
let subscription = "type Subscription {\n";
if (partialInfo.isPartial) {
schema += `type ${this.namedSubscriptionModelName} {
name: String
objectType: String!
query: String!
createdAt: Date
updatedAt: Date
expiresAt: Date
timeToLive: Float
}\n`;
schema += `type ${this.namedSubscriptionModelName}${this.collectionModelSuffix} {
count: Int!
items: [${this.namedSubscriptionModelName}!]
}\n`;
query += this.setupListPartialSubscriptions(queryResolver);
mutation += this.setupDeletePartialSubscription(mutationResolver);
}
for (const [type, propertyInfo] of types) {
const camelCasedType = this.camelcase(type);
const pluralType = this.pluralize(camelCasedType);
query += this.setupGetAllObjects(queryResolver, type, pluralType);
mutation += this.setupAddObject(mutationResolver, type);
mutation += this.setupDeleteObjects(mutationResolver, type);
mutation += this.setupCreateObject(mutationResolver, type);
mutation += this.setupCreateObjects(mutationResolver, type);
if (partialInfo.isPartial) {
mutation += this.setupCreatePartialSubscription(mutationResolver, type);
}
subscription += this.setupSubscribeToQuery(subscriptionResolver, type, pluralType);
if (propertyInfo.pk) {
query += this.setupGetObjectByPK(queryResolver, type, camelCasedType, propertyInfo.pk);
mutation += this.setupUpdateObject(mutationResolver, type);
mutation += this.setupDiffUpdateObject(mutationResolver, type);
mutation += this.setupDeleteObject(mutationResolver, type, propertyInfo.pk);
}
extraResolvers[type] = this.linkingObjectsResolver;
}
query += "}\n\n";
mutation += "}\n\n";
subscription += "}";
schema += query;
schema += mutation;
schema += subscription;
extraResolvers[this.allRealmTypesName] = {
__resolveType: (_, __, info) => {
return this.resolveLinkingObjectsTypeArgument(info);
}
};
extraResolvers[`${this.allRealmTypesName}${this.collectionModelSuffix}`] = {
__resolveType: (_, __, info) => {
return `${this.resolveLinkingObjectsTypeArgument(info)}${this.collectionModelSuffix}`;
}
};
const result = graphql_tools_1.makeExecutableSchema({
typeDefs: schema,
resolvers: Object.assign({ Query: queryResolver, Mutation: mutationResolver, Subscription: subscriptionResolver, [this.base64Type.name]: this.base64Type, [this.dateType.name]: this.dateType }, extraResolvers),
});
if (this.schemaCache) {
this.schemaCache.set(path, result);
}
return result;
}
setupGetAllObjects(queryResolver, type, pluralType) {
queryResolver[pluralType] = (_, args, context) => {
this.validateRead(context);
let result = context.realm.objects(type);
if (args.query) {
result = result.filtered(args.query);
}
if (args.sortBy) {
const descending = args.descending || false;
result = result.sorted(args.sortBy, descending);
}
return this.getCollectionResponse(result, args);
};
return `${pluralType}(query: String, sortBy: String, descending: Boolean, skip: Int, take: Int): ${this.getCollectionType(type)}\n`;
}
setupAddObject(mutationResolver, type) {
mutationResolver[`add${type}`] = (_, args, context) => {
this.validateWrite(context);
return this.createObjects(context.realm, type, [args.input], "NEVER")[0];
};
return `add${type}(input: ${type}${this.inputModelSuffix}): ${type} @deprecated(reason: "Use create${type} with 'updatePolicy: NEVER'")\n`;
}
setupUpdateObject(mutationResolver, type) {
mutationResolver[`update${type}`] = (_, args, context) => {
this.validateWrite(context);
return this.createObjects(context.realm, type, [args.input], "ALL")[0];
};
return `update${type}(input: ${type}${this.inputModelSuffix}): ${type} @deprecated(reason: "Use create${type} with 'updatePolicy: ALL'")\n`;
}
setupDiffUpdateObject(mutationResolver, type) {
mutationResolver[`diffUpdate${type}`] = (_, args, context) => {
this.validateWrite(context);
return this.createObjects(context.realm, type, [args.input], "MODIFIED")[0];
};
return `diffUpdate${type}(input: ${type}${this.inputModelSuffix}): ${type} @deprecated(reason: "Use create${type} with 'updatePolicy: MODIFIED'")\n`;
}
setupCreateObject(mutationResolver, type) {
mutationResolver[`create${type}`] = (_, args, context) => {
this.validateWrite(context);
return this.createObjects(context.realm, type, [args.input], args.updatePolicy)[0];
};
return `create${type}(input: ${type}${this.inputModelSuffix}!, updatePolicy: ${this.updatePolicyModelName}): ${type}!\n`;
}
setupCreateObjects(mutationResolver, type) {
const pluralType = this.pluralize(type);
mutationResolver[`create${pluralType}`] = (_, args, context) => {
this.validateWrite(context);
return this.createObjects(context.realm, type, args.input, args.updatePolicy);
};
return `create${pluralType}(input: [${type}${this.inputModelSuffix}!], updatePolicy: ${this.updatePolicyModelName}): [${type}!]\n`;
}
setupSubscribeToQuery(subscriptionResolver, type, pluralType) {
subscriptionResolver[pluralType] = {
subscribe: (_, args, context) => {
this.validateRead(context);
let result = context.realm.objects(type);
if (args.query) {
result = result.filtered(args.query);
}
if (args.sortBy) {
const descending = args.descending || false;
result = result.sorted(args.sortBy, descending);
}
const opId = context.operationId;
this.querySubscriptions[opId] = {
results: result,
realm: context.realm,
};
result.addListener((collection, change) => {
const payload = {};
payload[pluralType] = this.getCollectionResponse(collection, args);
this.pubsub.publish(opId, payload);
});
return this.pubsub.asyncIterator(opId);
},
};
return `${pluralType}(query: String, sortBy: String, descending: Boolean, skip: Int, take: Int): ${this.getCollectionType(type)}\n`;
}
setupGetObjectByPK(queryResolver, type, camelCasedType, pk) {
queryResolver[camelCasedType] = (_, args, context) => {
this.validateRead(context);
return context.realm.objectForPrimaryKey(type, args[pk.name]);
};
return `${camelCasedType}(${pk.name}: ${pk.type}): ${type}\n`;
}
setupDeleteObject(mutationResolver, type, pk) {
mutationResolver[`delete${type}`] = (_, args, context) => {
this.validateWrite(context);
let result = false;
context.realm.write(() => {
const obj = context.realm.objectForPrimaryKey(type, args[pk.name]);
if (obj) {
context.realm.delete(obj);
result = true;
}
});
return result;
};
return `delete${type}(${pk.name}: ${pk.type}): Boolean\n`;
}
setupDeleteObjects(mutationResolver, type) {
const pluralType = this.pluralize(type);
mutationResolver[`delete${pluralType}`] = (_, args, context) => {
this.validateWrite(context);
let result;
context.realm.write(() => {
let toDelete = context.realm.objects(type);
if (args.query) {
toDelete = toDelete.filtered(args.query);
}
result = toDelete.length;
context.realm.delete(toDelete);
});
return result;
};
return `delete${pluralType}(query: String): Int\n`;
}
setupCreatePartialSubscription(mutationResolver, type) {
mutationResolver[`create${type}Subscription`] = (_, args, context) => __awaiter(this, void 0, void 0, function* () {
let result = context.realm.objects(type);
if (args.query) {
result = result.filtered(args.query);
}
if (args.sortBy) {
const descending = args.descending || false;
result = result.sorted(args.sortBy, descending);
}
let subscription;
if (args.name) {
subscription = result.subscribe({
name: args.name,
update: args.update,
timeToLive: args.timeToLive,
});
}
else {
if (args.update !== undefined || args.timeToLive !== undefined) {
throw new Error("update and timeToLive are only supported when a name is provided.");
}
subscription = result.subscribe();
}
yield new Promise((resolve, reject) => {
subscription.addListener((s, state) => {
switch (state) {
case realmUtil_1.Realm.Sync.SubscriptionState.Complete:
subscription.removeAllListeners();
resolve();
break;
case realmUtil_1.Realm.Sync.SubscriptionState.Error:
subscription.removeAllListeners();
reject(subscription.error);
break;
}
});
});
return this.getCollectionResponse(result, {});
});
return `create${type}Subscription(query: String, sortBy: String, descending: Boolean, name: String, update: Boolean, timeToLive: Float): ${this.getCollectionType(type)}\n`;
}
setupListPartialSubscriptions(queryResolver) {
queryResolver.queryBasedSubscriptions = (_, args, context) => __awaiter(this, void 0, void 0, function* () {
const result = context.realm.subscriptions(args.name).map(s => {
const anyS = s;
return {
name: s.name,
objectType: anyS.matches_property.replace(this.subscriptionObjectNameRegex, "$2"),
query: s.query,
createdAt: anyS.created_at,
updatedAt: anyS.updated_at,
expiresAt: anyS.expires_at,
timeToLive: anyS.time_to_live,
};
});
return this.getCollectionResponse(result, {});
});
return `queryBasedSubscriptions(name: String): ${this.getCollectionType(this.namedSubscriptionModelName)}\n`;
}
setupDeletePartialSubscription(mutationResolver) {
mutationResolver.deleteQueryBasedSubscription = (_, args, context) => __awaiter(this, void 0, void 0, function* () {
context.realm.unsubscribe(args.name);
return true;
});
return "deleteQueryBasedSubscription(name: String!): Boolean\n";
}
createObjects(realm, type, objects, updatePolicy = "NEVER") {
const result = [];
const updateMode = updatePolicy.toLowerCase();
realm.write(() => {
for (const obj of objects) {
result.push(realm.create(type, obj, updateMode));
}
});
return result;
}
getPropertySchema(obj) {
let schemaProperties = "";
let inputSchemaProperties = "";
let primaryKey = null;
for (const key in obj.properties) {
if (!obj.properties.hasOwnProperty(key) ||
this.isReserved(key)) {
continue;
}
const prop = obj.properties[key];
if (prop.type === "linkingObjects") {
continue;
}
const types = this.getTypeString(prop);
if (!types || this.isReserved(types.type)) {
continue;
}
schemaProperties += `${key}: ${types.type}\n`;
inputSchemaProperties += `${key}: ${types.inputType}\n`;
if (key === obj.primaryKey) {
primaryKey = {
name: key,
type: types.type,
};
}
}
return {
propertySchema: schemaProperties,
inputPropertySchema: inputSchemaProperties,
pk: primaryKey,
};
}
getTypeString(prop) {
let type;
let inputType;
switch (prop.type) {
case "object":
type = prop.objectType;
inputType = `${prop.objectType}${this.inputModelSuffix}`;
break;
case "list":
const innerType = this.getPrimitiveTypeString(prop.objectType, prop.optional);
if (this.isReserved(innerType)) {
return undefined;
}
type = `[${innerType}]`;
inputType = this.isPrimitiveType(prop.objectType) ? type : `[${innerType}${this.inputModelSuffix}]`;
break;
default:
type = this.getPrimitiveTypeString(prop.type, prop.optional);
inputType = this.getPrimitiveTypeString(prop.type, true);
break;
}
return {
type,
inputType,
};
}
getPrimitiveTypeString(prop, optional) {
let result = "";
switch (prop) {
case "bool":
result = "Boolean";
break;
case "int":
result = this.presentIntsAsFloatsInSchema ? "Float" : "Int";
break;
case "float":
case "double":
result = "Float";
break;
case "date":
result = this.dateType.name;
break;
case "string":
result = "String";
break;
case "data":
result = this.base64Type.name;
break;
default:
return prop;
}
if (!optional) {
result += "!";
}
return result;
}
isPrimitiveType(type) {
switch (type) {
case "bool":
case "int":
case "float":
case "double":
case "date":
case "string":
case "data":
return true;
default:
return false;
}
}
getCollectionType(type) {
return this.includeCountInResponses ? `${type}${this.collectionModelSuffix}` : `[${type}!]`;
}
getCollectionResponse(collection, args) {
let result = collection;
if (args.skip || args.take) {
const skip = args.skip || 0;
const take = args.take ? (args.take + skip) : undefined;
result = collection.slice(skip, take);
}
if (this.includeCountInResponses) {
return {
count: collection.length,
items: result,
};
}
return result;
}
resolveLinkingObjectsTypeArgument(info) {
const linkingObjectsNode = info.fieldNodes.find(n => n.name.value === "_linkingObjects");
if (!linkingObjectsNode) {
throw new Error(`${this.allRealmTypesName} is only supported as the return type of _linkingObjects call.`);
}
const objectTypeNode = linkingObjectsNode.arguments.find(a => a.name.value === "objectType").value;
return objectTypeNode.value;
}
camelcase(value) {
return value.charAt(0).toLowerCase() + value.slice(1);
}
pluralize(value) {
const result = pluralize(value);
if (result !== value) {
return result;
}
return result + "s";
}
isReserved(value) {
return value.startsWith("__");
}
openRealm(path, user) {
return __awaiter(this, void 0, void 0, function* () {
try {
const realm = yield this.server.openRealm({
remotePath: path,
schema: undefined,
user,
forceCloseHandler: {
id: "GraphQLService",
handler: (deleteAfterClose) => {
if (deleteAfterClose) {
this.schemaCache.del(this.getPath(path));
}
}
}
});
if (this.schemaCache) {
realm.addListener("schema", this.getSchemaHandler(path));
}
return realm;
}
catch (err) {
if (err instanceof RealmProblem_1.RealmProblem) {
err.title = `${err.message} (${err.status}): ${err.detail}`;
}
throw err;
}
});
}
getSchemaHandler(path) {
let value = this.schemaHandlers[path];
if (!value) {
value = (realm, event, schema) => {
try {
this.schemaCache.del(path);
this.getSchema(path, realm);
}
catch (_a) {
}
};
this.schemaHandlers[path] = value;
}
return value;
}
};
__decorate([
decorators_1.Upgrade("/:path+"),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object, Object]),
__metadata("design:returntype", Promise)
], GraphQLService.prototype, "subscriptionHandler", null);
__decorate([
decorators_1.Get("/explore/*"),
__param(0, decorators_1.Request()), __param(1, decorators_1.Response()), __param(2, decorators_1.Next()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object, Object]),
__metadata("design:returntype", Promise)
], GraphQLService.prototype, "getExplore", null);
__decorate([
decorators_1.Post("/explore/*"),
__param(0, decorators_1.Request()), __param(1, decorators_1.Response()), __param(2, decorators_1.Next()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object, Object]),
__metadata("design:returntype", Promise)
], GraphQLService.prototype, "postExplore", null);
__decorate([
decorators_1.Get("/*"),
__param(0, decorators_1.Request()), __param(1, decorators_1.Response()), __param(2, decorators_1.Next()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object, Object]),
__metadata("design:returntype", Promise)
], GraphQLService.prototype, "get", null);
__decorate([
decorators_1.Post("/*"),
__param(0, decorators_1.Request()), __param(1, decorators_1.Response()), __param(2, decorators_1.Next()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object, Object]),
__metadata("design:returntype", Promise)
], GraphQLService.prototype, "post", null);
__decorate([
decorators_1.Delete("/schema/*"),
__param(0, decorators_1.Request()), __param(1, decorators_1.Response()),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object, Object]),
__metadata("design:returntype", Promise)
], GraphQLService.prototype, "deleteSchema", null);
GraphQLService = __decorate([
decorators_1.BaseRoute("/graphql"),
decorators_1.Cors("/"),
__metadata("design:paramtypes", [Object])
], GraphQLService);
exports.GraphQLService = GraphQLService;
//# sourceMappingURL=GraphQLService.js.map