Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
"version": "9.6.0-alpha.33",
"description": "An express module providing a Parse-compatible API server",
"main": "lib/index.js",
"exports": {
".": "./lib/index.js",
"./cloud": "./lib/cloud.js"
},
"repository": {
"type": "git",
"url": "https://github.com/parse-community/parse-server"
Expand Down
38 changes: 12 additions & 26 deletions src/ParseServer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
var batch = require('./batch'),
express = require('express'),
middlewares = require('./middlewares'),
Parse = require('parse/node').Parse,
{ parse } = require('graphql'),
path = require('path'),
fs = require('fs');
Expand Down Expand Up @@ -46,9 +45,8 @@ import Deprecator from './Deprecator/Deprecator';
import { DefinedSchemas } from './SchemaMigrations/DefinedSchemas';
import OptionsDefinitions from './Options/Definitions';
import { resolvingPromise, Connections } from './TestUtils';

// Mutate the Parse object to add the Cloud Code handlers
addParseCloud();
import { CloudCodeRegistrar } from './cloud-code/CloudCodeRegistrar';
import { LegacyCloud } from './cloud-code/LegacyCloud';

// Track connections to destroy them on shutdown
const connections = new Connections();
Expand Down Expand Up @@ -128,9 +126,11 @@ class ParseServer {
javascriptKey,
serverURL = requiredParameter('You must provide a serverURL!'),
} = options;
// Initialize the node client SDK automatically
Parse.initialize(appId, javascriptKey || 'unused', masterKey);
Parse.serverURL = serverURL;
// Initialize the cloud SDK and register it
const cloudSDK = new LegacyCloud();
cloudSDK.initialize({ appId, masterKey, javascriptKey, serverURL });
CloudCodeRegistrar.setInstance(cloudSDK);
const Parse = cloudSDK.Parse;
Config.validateOptions(options);
const allControllers = controllers.getControllers(options);

Expand Down Expand Up @@ -163,6 +163,8 @@ class ParseServer {
schema,
liveQueryController,
} = this.config;
const cloudSDK = CloudCodeRegistrar.getInstance(this.config.appId) as LegacyCloud;
const Parse = cloudSDK.Parse;
try {
await databaseController.performInitialization();
} catch (e) {
Expand All @@ -185,7 +187,7 @@ class ParseServer {
startupPromises.push(liveQueryController.connect());
await Promise.all(startupPromises);
if (cloud) {
addParseCloud();
cloudSDK.bindToParseCloud();
if (typeof cloud === 'function') {
await Promise.resolve(cloud(Parse));
} else if (typeof cloud === 'string') {
Expand Down Expand Up @@ -373,6 +375,7 @@ class ParseServer {
});
}
if (process.env.PARSE_SERVER_ENABLE_EXPERIMENTAL_DIRECT_ACCESS === '1' || directAccess) {
const Parse = require('parse/node').Parse;
Parse.CoreManager.setRESTController(ParseServerRESTController(appId, appRouter));
}
return api;
Expand Down Expand Up @@ -535,6 +538,7 @@ class ParseServer {
}

static async verifyServerUrl() {
const Parse = require('parse/node').Parse;
// perform a health check on the serverURL value
if (Parse.serverURL) {
const isValidHttpUrl = string => {
Expand Down Expand Up @@ -577,24 +581,6 @@ class ParseServer {
}
}

function addParseCloud() {
const ParseCloud = require('./cloud-code/Parse.Cloud');
const ParseServer = require('./cloud-code/Parse.Server');
Object.defineProperty(Parse, 'Server', {
get() {
const conf = Config.get(Parse.applicationId);
return { ...conf, ...ParseServer };
},
set(newVal) {
newVal.appId = Parse.applicationId;
Config.put(newVal);
},
configurable: true,
});
Object.assign(Parse.Cloud, ParseCloud);
global.Parse = Parse;
}

function injectDefaults(options: ParseServerOptions) {
Object.keys(defaults).forEach(key => {
if (!Object.prototype.hasOwnProperty.call(options, key)) {
Expand Down
108 changes: 108 additions & 0 deletions src/cloud-code/CloudCodeRegistrar.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
/**
* CloudCodeRegistrar defines the contract for registering cloud code hooks.
*
* Parse Server creates one during startup. BYO SDKs implement this to
* register their own hooks without depending on parse/node.
*
* Usage from any JS SDK:
*
* import { CloudCodeRegistrar, TriggerType } from 'parse-server/cloud';
* const registrar = CloudCodeRegistrar.getInstance('myAppId');
* registrar.define(HookType.function, 'hello', handler);
* registrar.defineTrigger(TriggerType.beforeSave, 'GameScore', handler);
*/

import type { TriggerHandlerMap, HookHandlerMap } from './types';

export const TriggerType = {
beforeSave: 'beforeSave',
afterSave: 'afterSave',
beforeDelete: 'beforeDelete',
afterDelete: 'afterDelete',
beforeFind: 'beforeFind',
afterFind: 'afterFind',
beforeLogin: 'beforeLogin',
afterLogin: 'afterLogin',
afterLogout: 'afterLogout',
beforePasswordResetRequest: 'beforePasswordResetRequest',
beforeConnect: 'beforeConnect',
beforeSubscribe: 'beforeSubscribe',
afterEvent: 'afterEvent',
} as const;

export type TriggerType = typeof TriggerType[keyof typeof TriggerType];

export const HookType = {
function: 'function',
job: 'job',
} as const;

export type HookType = typeof HookType[keyof typeof HookType];

export abstract class CloudCodeRegistrar {
private static _instances: Map<string, CloudCodeRegistrar> = new Map();

abstract get appId(): string;

abstract initialize(config: RegistrarConfig): void;

/**
* Register a cloud function or job.
*
* The handler type is derived from the `HookType` constant:
* - `HookType.function` → `FunctionHandler<P>`
* - `HookType.job` → `JobHandler<P>`
*/
abstract define<
K extends HookType,
P extends Record<string, unknown> = Record<string, unknown>,
>(type: K, name: string, handler: HookHandlerMap<P>[K], validator?: unknown): void;

/**
* Register a trigger on a class (beforeSave, afterDelete, etc.),
* a connect trigger (beforeConnect with className '@Connect'),
* or a live query handler (afterEvent, beforeSubscribe, etc.).
*
* The handler type is derived from the `TriggerType` constant:
* - `TriggerType.beforeSave` → `ObjectTriggerHandler<T>`
* - `TriggerType.beforeFind` → `QueryTriggerHandler`
* - `TriggerType.afterFind` → `AfterFindHandler<T>`
* - etc.
*/
abstract defineTrigger<
K extends TriggerType,
T extends Record<string, unknown> = Record<string, unknown>,
>(type: K, className: string, handler: TriggerHandlerMap<T>[K], validator?: unknown): void;

/**
* Remove all registered hooks.
*/
abstract removeAllHooks(): void;

static getInstance(appId: string): CloudCodeRegistrar {
const instance = CloudCodeRegistrar._instances.get(appId);
if (!instance) {
throw new Error(`CloudCodeRegistrar not found for appId "${appId}".`);
}
return instance;
}

static setInstance(registrar: CloudCodeRegistrar): void {
CloudCodeRegistrar._instances.set(registrar.appId, registrar);
}

static removeInstance(appId: string): void {
CloudCodeRegistrar._instances.delete(appId);
}

static clearInstances(): void {
CloudCodeRegistrar._instances.clear();
}
}

export interface RegistrarConfig {
appId: string;
masterKey: string;
javascriptKey?: string;
serverURL: string;
}
Loading