modifies create and update hooks to 'change' instead

This commit is contained in:
James
2020-07-29 19:19:15 -04:00
parent 749388e877
commit 16b9aab785
14 changed files with 223 additions and 208 deletions

View File

@@ -15,14 +15,6 @@ module.exports = {
delete: () => true,
},
hooks: {
beforeCreate: [
(operation) => {
if (operation.req.headers.hook === 'beforeCreate') {
operation.req.body.description += '-beforeCreateSuffix';
}
return operation.data;
},
],
beforeRead: [
(operation) => {
if (operation.req.headers.hook === 'beforeRead') {
@@ -30,10 +22,10 @@ module.exports = {
}
},
],
beforeUpdate: [
beforeChange: [
(operation) => {
if (operation.req.headers.hook === 'beforeUpdate') {
operation.req.body.description += '-beforeUpdateSuffix';
if (operation.req.headers.hook === 'beforeChange') {
operation.req.body.description += '-beforeChangeSuffix';
}
return operation.data;
},
@@ -46,14 +38,6 @@ module.exports = {
}
},
],
afterCreate: [
(operation) => {
if (operation.req.headers.hook === 'afterCreate') {
operation.doc.afterCreateHook = true;
}
return operation.doc;
},
],
afterRead: [
(operation) => {
const { doc } = operation;
@@ -62,10 +46,10 @@ module.exports = {
return doc;
},
],
afterUpdate: [
afterChange: [
(operation) => {
if (operation.req.headers.hook === 'afterUpdate') {
operation.doc.afterUpdateHook = true;
if (operation.req.headers.hook === 'afterChange') {
operation.doc.afterChangeHook = true;
}
return operation.doc;
},

View File

@@ -27,31 +27,47 @@ async function register(args) {
}
// /////////////////////////////////////
// 2. Execute field-level hooks, access, and validation
// 2. Execute before validate collection hooks
// /////////////////////////////////////
data = await this.performFieldOperations(collectionConfig, {
data,
hook: 'beforeCreate',
operationName: 'create',
req,
});
// /////////////////////////////////////
// 3. Execute before create hook
// /////////////////////////////////////
await collectionConfig.hooks.beforeCreate.reduce(async (priorHook, hook) => {
await collectionConfig.hooks.beforeValidate.reduce(async (priorHook, hook) => {
await priorHook;
data = (await hook({
data,
req,
operation: 'create',
})) || data;
}, Promise.resolve());
// /////////////////////////////////////
// 3. Execute field-level hooks, access, and validation
// /////////////////////////////////////
data = await this.performFieldOperations(collectionConfig, {
data,
hook: 'beforeChange',
operation: 'create',
req,
});
// /////////////////////////////////////
// 4. Execute before create hook
// /////////////////////////////////////
await collectionConfig.hooks.beforeChange.reduce(async (priorHook, hook) => {
await priorHook;
data = (await hook({
data,
req,
operation: 'create',
})) || data;
}, Promise.resolve());
// /////////////////////////////////////
// 6. Perform register
// 5. Perform register
// /////////////////////////////////////
const modelData = { ...data };
@@ -72,32 +88,33 @@ async function register(args) {
result = result.toJSON({ virtuals: true });
// /////////////////////////////////////
// 7. Execute field-level hooks and access
// 6. Execute field-level hooks and access
// /////////////////////////////////////
result = await this.performFieldOperations(collectionConfig, {
data: result,
hook: 'afterRead',
operationName: 'read',
operation: 'read',
req,
depth,
});
// /////////////////////////////////////
// 8. Execute after create hook
// 7. Execute after create hook
// /////////////////////////////////////
await collectionConfig.hooks.afterCreate.reduce(async (priorHook, hook) => {
await collectionConfig.hooks.afterChange.reduce(async (priorHook, hook) => {
await priorHook;
result = await hook({
doc: result,
req: args.req,
operation: 'create',
}) || result;
}, Promise.resolve());
// /////////////////////////////////////
// 9. Return user
// 8. Return user
// /////////////////////////////////////
return result;

View File

@@ -4,8 +4,6 @@ const { NotFound, Forbidden } = require('../../errors');
const executeAccess = require('../executeAccess');
async function update(args) {
const { config } = this;
const {
depth,
collection: {
@@ -54,39 +52,55 @@ async function update(args) {
let { data } = args;
// /////////////////////////////////////
// 2. Execute field-level hooks, access, and validation
// 3. Execute before validate collection hooks
// /////////////////////////////////////
await collectionConfig.hooks.beforeValidate.reduce(async (priorHook, hook) => {
await priorHook;
data = (await hook({
data,
req,
operation: 'update',
originalDoc,
})) || data;
}, Promise.resolve());
// /////////////////////////////////////
// 4. Execute field-level hooks, access, and validation
// /////////////////////////////////////
data = await this.performFieldOperations(collectionConfig, {
data,
req,
hook: 'beforeUpdate',
operationName: 'update',
hook: 'beforeChange',
operation: 'update',
originalDoc,
});
// /////////////////////////////////////
// 3. Execute before update hook
// 5. Execute before update hook
// /////////////////////////////////////
await collectionConfig.hooks.beforeUpdate.reduce(async (priorHook, hook) => {
await collectionConfig.hooks.beforeChange.reduce(async (priorHook, hook) => {
await priorHook;
data = (await hook({
data,
req,
originalDoc,
operation: 'update',
})) || data;
}, Promise.resolve());
// /////////////////////////////////////
// 4. Merge updates into existing data
// 6. Merge updates into existing data
// /////////////////////////////////////
data = deepmerge(originalDoc, data, { arrayMerge: overwriteMerge });
// /////////////////////////////////////
// 5. Handle password update
// 7. Handle password update
// /////////////////////////////////////
const dataToUpdate = { ...data };
@@ -98,7 +112,7 @@ async function update(args) {
}
// /////////////////////////////////////
// 6. Perform database operation
// 8. Perform database operation
// /////////////////////////////////////
Object.assign(user, dataToUpdate);
@@ -108,32 +122,33 @@ async function update(args) {
user = user.toJSON({ virtuals: true });
// /////////////////////////////////////
// 7. Execute field-level hooks and access
// 9. Execute field-level hooks and access
// /////////////////////////////////////
user = this.performFieldOperations(collectionConfig, {
data: user,
hook: 'afterRead',
operationName: 'read',
operation: 'read',
req,
depth,
});
// /////////////////////////////////////
// 8. Execute after update hook
// 10. Execute after update hook
// /////////////////////////////////////
await collectionConfig.hooks.afterUpdate.reduce(async (priorHook, hook) => {
await collectionConfig.hooks.afterChange.reduce(async (priorHook, hook) => {
await priorHook;
user = await hook({
doc: user,
req,
operation: 'update',
}) || user;
}, Promise.resolve());
// /////////////////////////////////////
// 9. Return user
// 11. Return user
// /////////////////////////////////////
return user;

View File

@@ -33,31 +33,46 @@ async function create(args) {
await executeAccess({ req }, collectionConfig.access.create);
// /////////////////////////////////////
// 2. Execute field-level access, hooks, and validation
// 2. Execute before validate collection hooks
// /////////////////////////////////////
data = await performFieldOperations(collectionConfig, {
data,
hook: 'beforeCreate',
operationName: 'create',
req,
});
// /////////////////////////////////////
// 3. Execute before collection hook
// /////////////////////////////////////
await collectionConfig.hooks.beforeCreate.reduce(async (priorHook, hook) => {
await collectionConfig.hooks.beforeValidate.reduce(async (priorHook, hook) => {
await priorHook;
data = (await hook({
data,
req,
operation: 'create',
})) || data;
}, Promise.resolve());
// /////////////////////////////////////
// 4. Upload and resize any files that may be present
// 3. Execute field-level access, beforeChange hooks, and validation
// /////////////////////////////////////
data = await performFieldOperations(collectionConfig, {
data,
hook: 'beforeChange',
operation: 'create',
req,
});
// /////////////////////////////////////
// 4. Execute before change hooks
// /////////////////////////////////////
await collectionConfig.hooks.beforeChange.reduce(async (priorHook, hook) => {
await priorHook;
data = (await hook({
data,
req,
operation: 'create',
})) || data;
}, Promise.resolve());
// /////////////////////////////////////
// 5. Upload and resize any files that may be present
// /////////////////////////////////////
if (collectionConfig.upload) {
@@ -98,7 +113,7 @@ async function create(args) {
}
// /////////////////////////////////////
// 5. Perform database operation
// 6. Perform database operation
// /////////////////////////////////////
let result = new Model();
@@ -113,32 +128,33 @@ async function create(args) {
result = result.toJSON({ virtuals: true });
// /////////////////////////////////////
// 6. Execute field-level hooks and access
// 7. Execute field-level hooks and access
// /////////////////////////////////////
result = await performFieldOperations(collectionConfig, {
data: result,
hook: 'afterRead',
operationName: 'read',
operation: 'read',
req,
depth,
});
// /////////////////////////////////////
// 7. Execute after collection hook
// 8. Execute after collection hook
// /////////////////////////////////////
await collectionConfig.hooks.afterCreate.reduce(async (priorHook, hook) => {
await collectionConfig.hooks.afterChange.reduce(async (priorHook, hook) => {
await priorHook;
result = await hook({
doc: result,
req: args.req,
operation: 'create',
}) || result;
}, Promise.resolve());
// /////////////////////////////////////
// 8. Return results
// 9. Return results
// /////////////////////////////////////
return result;

View File

@@ -97,7 +97,7 @@ async function deleteQuery(args) {
result = await this.performFieldOperations(collectionConfig, {
data: result,
hook: 'afterRead',
operationName: 'read',
operation: 'read',
req,
depth,
});

View File

@@ -99,7 +99,7 @@ async function find(args) {
data,
req,
hook: 'afterRead',
operationName: 'read',
operation: 'read',
overrideAccess,
},
find,

View File

@@ -81,7 +81,7 @@ async function findByID(args) {
req,
data: result,
hook: 'afterRead',
operationName: 'read',
operation: 'read',
currentDepth,
});

View File

@@ -63,42 +63,58 @@ async function update(args) {
const originalDoc = doc.toJSON({ virtuals: true });
let { data } = args;
// /////////////////////////////////////
// 2. Execute field-level hooks, access, and validation
// 3. Execute before validate collection hooks
// /////////////////////////////////////
let { data } = args;
await collectionConfig.hooks.beforeValidate.reduce(async (priorHook, hook) => {
await priorHook;
data = (await hook({
data,
req,
operation: 'update',
originalDoc,
})) || data;
}, Promise.resolve());
// /////////////////////////////////////
// 4. Execute field-level hooks, access, and validation
// /////////////////////////////////////
data = await this.performFieldOperations(collectionConfig, {
data,
req,
originalDoc,
hook: 'beforeUpdate',
operationName: 'update',
hook: 'beforeChange',
operation: 'update',
});
// /////////////////////////////////////
// 3. Execute before update hook
// 5. Execute before update hook
// /////////////////////////////////////
await collectionConfig.hooks.beforeUpdate.reduce(async (priorHook, hook) => {
await collectionConfig.hooks.beforeChange.reduce(async (priorHook, hook) => {
await priorHook;
data = (await hook({
data,
req,
originalDoc,
operation: 'update',
})) || data;
}, Promise.resolve());
// /////////////////////////////////////
// 4. Merge updates into existing data
// 6. Merge updates into existing data
// /////////////////////////////////////
data = deepmerge(originalDoc, data, { arrayMerge: overwriteMerge });
// /////////////////////////////////////
// 5. Upload and resize any files that may be present
// 7. Upload and resize any files that may be present
// /////////////////////////////////////
if (collectionConfig.upload) {
@@ -141,7 +157,7 @@ async function update(args) {
}
// /////////////////////////////////////
// 6. Perform database operation
// 8. Perform database operation
// /////////////////////////////////////
Object.assign(doc, data);
@@ -151,32 +167,33 @@ async function update(args) {
doc = doc.toJSON({ virtuals: true });
// /////////////////////////////////////
// 7. Execute field-level hooks and access
// 9. Execute field-level hooks and access
// /////////////////////////////////////
doc = await this.performFieldOperations(collectionConfig, {
data: doc,
hook: 'afterRead',
operationName: 'read',
operation: 'read',
req,
depth,
});
// /////////////////////////////////////
// 8. Execute after collection hook
// 10. Execute after collection hook
// /////////////////////////////////////
await collectionConfig.hooks.afterUpdate.reduce(async (priorHook, hook) => {
await collectionConfig.hooks.afterChange.reduce(async (priorHook, hook) => {
await priorHook;
doc = await hook({
doc,
req,
operation: 'update',
}) || doc;
}, Promise.resolve());
// /////////////////////////////////////
// 9. Return updated document
// 11. Return updated document
// /////////////////////////////////////
return doc;

View File

@@ -406,8 +406,8 @@ describe('Collections - REST', () => {
expect(getResponse.status).toBe(200);
expect(data.docs).toHaveLength(2);
expect(data.docs[0].id).toEqual(id1);
expect(data.docs[1].id).toEqual(id2);
expect(data.docs[0].id).toStrictEqual(id1);
expect(data.docs[1].id).toStrictEqual(id2);
// Query on shared desc and sort descending
const getResponseSorted = await fetch(`${url}/api/localized-posts?where[description][equals]=${desc}&sort=-title`);
@@ -416,14 +416,14 @@ describe('Collections - REST', () => {
expect(getResponse.status).toBe(200);
expect(sortedData.docs).toHaveLength(2);
// Opposite order from first request
expect(sortedData.docs[0].id).toEqual(id2);
expect(sortedData.docs[1].id).toEqual(id1);
expect(sortedData.docs[0].id).toStrictEqual(id2);
expect(sortedData.docs[1].id).toStrictEqual(id1);
});
});
describe('Hooks', () => {
describe('Before', () => {
it('beforeCreate', async () => {
it('beforeChange', async () => {
const response = await fetch(`${url}/api/hooks`, {
body: JSON.stringify({
title: faker.name.firstName(),
@@ -433,48 +433,13 @@ describe('Collections - REST', () => {
headers: {
Authorization: `JWT ${token}`,
'Content-Type': 'application/json',
hook: 'beforeCreate', // Used by hook
hook: 'beforeChange', // Used by hook
},
method: 'post',
});
const data = await response.json();
expect(response.status).toBe(201);
expect(data.doc.description).toEqual('Original-beforeCreateSuffix');
});
it('beforeUpdate', async () => {
const createResponse = await fetch(`${url}/api/hooks`, {
body: JSON.stringify({
title: faker.name.firstName(),
description: 'Original',
priority: 1,
}),
headers: {
Authorization: `JWT ${token}`,
'Content-Type': 'application/json',
},
method: 'post',
});
const createData = await createResponse.json();
expect(createResponse.status).toBe(201);
const { id } = createData.doc;
const response = await fetch(`${url}/api/hooks/${id}`, {
body: JSON.stringify({
description: 'Updated',
}),
headers: {
Authorization: `JWT ${token}`,
'Content-Type': 'application/json',
hook: 'beforeUpdate', // Used by hook
},
method: 'put',
});
const data = await response.json();
expect(response.status).toBe(200);
expect(data.doc.description).toEqual('Updated-beforeUpdateSuffix');
expect(data.doc.description).toStrictEqual('Original-beforeChangeSuffix');
});
it('beforeDelete', async () => {
@@ -505,30 +470,11 @@ describe('Collections - REST', () => {
const data = await response.json();
expect(response.status).toBe(200);
// Intentionally afterDeleteHook - beforeDelete hook is setting header in order to trigger afterDelete hook
expect(data.afterDeleteHook).toEqual(true);
expect(data.afterDeleteHook).toStrictEqual(true);
});
});
describe('After', () => {
it('afterCreate', async () => {
const response = await fetch(`${url}/api/hooks`, {
body: JSON.stringify({
title: faker.name.firstName(),
description: 'Original',
priority: 1,
}),
headers: {
Authorization: `JWT ${token}`,
'Content-Type': 'application/json',
hook: 'afterCreate', // Used by hook
},
method: 'post',
});
const data = await response.json();
expect(response.status).toBe(201);
expect(data.doc.afterCreateHook).toEqual(true);
});
it('afterRead', async () => {
const response = await fetch(`${url}/api/hooks`, {
body: JSON.stringify({
@@ -547,10 +493,10 @@ describe('Collections - REST', () => {
const getResponse = await fetch(`${url}/api/hooks/${data.doc.id}`);
const getResponseData = await getResponse.json();
expect(getResponse.status).toBe(200);
expect(getResponseData.afterReadHook).toEqual(true);
expect(getResponseData.afterReadHook).toStrictEqual(true);
});
it('afterUpdate', async () => {
it('afterChange', async () => {
const createResponse = await fetch(`${url}/api/hooks`, {
body: JSON.stringify({
title: faker.name.firstName(),
@@ -568,19 +514,19 @@ describe('Collections - REST', () => {
const response = await fetch(`${url}/api/hooks/${id}`, {
body: JSON.stringify({
description: 'afterUpdate',
description: 'afterChange',
}),
headers: {
Authorization: `JWT ${token}`,
'Content-Type': 'application/json',
hook: 'afterUpdate', // Used by hook
hook: 'afterChange', // Used by hook
},
method: 'put',
});
const data = await response.json();
expect(response.status).toBe(200);
expect(data.doc.afterUpdateHook).toEqual(true);
expect(data.doc.afterChangeHook).toStrictEqual(true);
});
it('afterDelete', async () => {
@@ -610,7 +556,7 @@ describe('Collections - REST', () => {
const data = await response.json();
expect(response.status).toBe(200);
expect(data.afterDeleteHook).toEqual(true);
expect(data.afterDeleteHook).toStrictEqual(true);
});
});
});

View File

@@ -77,10 +77,9 @@ const sanitizeCollection = (collections, collection) => {
if (!sanitized.access) sanitized.access = {};
if (!sanitized.admin) sanitized.admin = {};
if (!sanitized.hooks.beforeCreate) sanitized.hooks.beforeCreate = [];
if (!sanitized.hooks.afterCreate) sanitized.hooks.afterCreate = [];
if (!sanitized.hooks.beforeUpdate) sanitized.hooks.beforeUpdate = [];
if (!sanitized.hooks.afterUpdate) sanitized.hooks.afterUpdate = [];
if (!sanitized.hooks.beforeValidate) sanitized.hooks.beforeValidate = [];
if (!sanitized.hooks.beforeChange) sanitized.hooks.beforeChange = [];
if (!sanitized.hooks.afterChange) sanitized.hooks.afterChange = [];
if (!sanitized.hooks.beforeRead) sanitized.hooks.beforeRead = [];
if (!sanitized.hooks.afterRead) sanitized.hooks.afterRead = [];
if (!sanitized.hooks.beforeDelete) sanitized.hooks.beforeDelete = [];
@@ -103,10 +102,14 @@ const sanitizeCollection = (collections, collection) => {
name: 'filename',
label: 'Filename',
hooks: {
beforeCreate: [
({ req }) => {
beforeChange: [
({ req, operation, value }) => {
if (operation === 'create') {
const file = (req.files && req.files.file) ? req.files.file : req.file;
return file.name;
}
return value;
},
],
},

View File

@@ -1,28 +1,28 @@
const { ValidationError } = require('../errors');
const executeAccess = require('../auth/executeAccess');
async function performFieldOperations(entityConfig, operation) {
async function performFieldOperations(entityConfig, args) {
const {
data: fullData,
originalDoc: fullOriginalDoc,
operationName,
operation,
hook,
req,
req: {
payloadAPI,
},
overrideAccess,
} = operation;
} = args;
const recursivePerformFieldOperations = performFieldOperations.bind(this);
let depth = 0;
if (payloadAPI === 'REST') {
depth = (operation.depth || operation.depth === 0) ? parseInt(operation.depth, 10) : this.config.defaultDepth;
depth = (args.depth || args.depth === 0) ? parseInt(args.depth, 10) : this.config.defaultDepth;
}
const currentDepth = operation.currentDepth || 1;
const currentDepth = args.currentDepth || 1;
const populateRelationship = async (dataReference, data, field, i) => {
const dataToUpdate = dataReference;
@@ -116,10 +116,10 @@ async function performFieldOperations(entityConfig, operation) {
const createAccessPromise = async (data, originalDoc, field) => {
const resultingData = data;
if (field.access && field.access[operationName]) {
const result = await field.access[operationName]({ req });
if (field.access && field.access[operation]) {
const result = await field.access[operation]({ req });
if (!result && operationName === 'update' && originalDoc[field.name] !== undefined) {
if (!result && operation === 'update' && originalDoc[field.name] !== undefined) {
resultingData[field.name] = originalDoc[field.name];
} else if (!result) {
delete resultingData[field.name];
@@ -175,7 +175,7 @@ async function performFieldOperations(entityConfig, operation) {
req,
data: relatedDocumentData,
hook: 'afterRead',
operationName: 'read',
operation: 'read',
});
await relatedCollection.hooks.afterRead.reduce(async (priorHook, currentHook) => {
@@ -221,7 +221,7 @@ async function performFieldOperations(entityConfig, operation) {
req,
data: relatedDocumentData,
hook: 'afterRead',
operationName: 'read',
operation: 'read',
});
await relatedCollection.hooks.afterRead.reduce(async (priorHook, currentHook) => {
@@ -285,7 +285,7 @@ async function performFieldOperations(entityConfig, operation) {
}
}
if (operationName === 'create' || operationName === 'update') {
if (operation === 'create' || operation === 'update') {
if (field.type === 'array' || field.type === 'blocks') {
const hasRowsOfNewData = Array.isArray(data[field.name]);
const newRowCount = hasRowsOfNewData ? data[field.name].length : 0;

View File

@@ -50,7 +50,7 @@ async function findOne(args) {
doc = this.performFieldOperations(globalConfig, {
data: doc,
hook: 'afterRead',
operationName: 'read',
operation: 'read',
req,
depth,
});

View File

@@ -3,7 +3,7 @@ const overwriteMerge = require('../../utilities/overwriteMerge');
const executeAccess = require('../../auth/executeAccess');
async function update(args) {
const { config, globals: { Model } } = this;
const { globals: { Model } } = this;
const {
globalConfig,
@@ -38,42 +38,57 @@ async function update(args) {
const globalJSON = global.toJSON({ virtuals: true });
// /////////////////////////////////////
// 3. Execute before global hook
// /////////////////////////////////////
let { data } = args;
await globalConfig.hooks.beforeUpdate.reduce(async (priorHook, hook) => {
// /////////////////////////////////////
// 3. Execute before validate collection hooks
// /////////////////////////////////////
await globalConfig.hooks.beforeValidate.reduce(async (priorHook, hook) => {
await priorHook;
data = (await hook({
data,
req,
operation: 'update',
})) || data;
}, Promise.resolve());
// /////////////////////////////////////
// 4. Execute field-level hooks, access, and validation
// /////////////////////////////////////
data = await this.performFieldOperations(globalConfig, {
data,
req,
hook: 'beforeChange',
operation: 'update',
originalDoc: global,
});
// /////////////////////////////////////
// 5. Execute before global hook
// /////////////////////////////////////
await globalConfig.hooks.beforeChange.reduce(async (priorHook, hook) => {
await priorHook;
data = (await hook({
data,
req,
originalDoc: global,
operation: 'update',
})) || data;
}, Promise.resolve());
// /////////////////////////////////////
// 4. Merge updates into existing data
// 6. Merge updates into existing data
// /////////////////////////////////////
data = deepmerge(globalJSON, data, { arrayMerge: overwriteMerge });
// /////////////////////////////////////
// 5. Execute field-level hooks, access, and validation
// /////////////////////////////////////
data = await this.performFieldOperations(globalConfig, {
data,
req,
hook: 'beforeUpdate',
operationName: 'update',
originalDoc: globalJSON,
});
// /////////////////////////////////////
// 6. Perform database operation
// 7. Perform database operation
// /////////////////////////////////////
Object.assign(global, data);
@@ -83,32 +98,33 @@ async function update(args) {
global = global.toJSON({ virtuals: true });
// /////////////////////////////////////
// 7. Execute field-level hooks and access
// 8. Execute field-level hooks and access
// /////////////////////////////////////
global = await this.performFieldOperations(globalConfig, {
data: global,
hook: 'afterRead',
operationName: 'read',
operation: 'read',
req,
depth,
});
// /////////////////////////////////////
// 8. Execute after global hook
// 9. Execute after global hook
// /////////////////////////////////////
await globalConfig.hooks.afterUpdate.reduce(async (priorHook, hook) => {
await globalConfig.hooks.afterChange.reduce(async (priorHook, hook) => {
await priorHook;
global = await hook({
doc: global,
req,
operation: 'update',
}) || global;
}, Promise.resolve());
// /////////////////////////////////////
// 9. Return global
// 10. Return global
// /////////////////////////////////////
return global;

View File

@@ -23,8 +23,9 @@ const sanitizeGlobals = (globals) => {
if (!sanitizedGlobal.access) sanitizedGlobal.access = {};
if (!sanitizedGlobal.admin) sanitizedGlobal.admin = {};
if (!sanitizedGlobal.hooks.beforeUpdate) sanitizedGlobal.hooks.beforeUpdate = [];
if (!sanitizedGlobal.hooks.afterUpdate) sanitizedGlobal.hooks.afterUpdate = [];
if (!sanitizedGlobal.hooks.beforeValidate) sanitizedGlobal.hooks.beforeValidate = [];
if (!sanitizedGlobal.hooks.beforeChange) sanitizedGlobal.hooks.beforeChange = [];
if (!sanitizedGlobal.hooks.afterChange) sanitizedGlobal.hooks.afterChange = [];
if (!sanitizedGlobal.hooks.beforeRead) sanitizedGlobal.hooks.beforeRead = [];
if (!sanitizedGlobal.hooks.afterRead) sanitizedGlobal.hooks.afterRead = [];