nestjs-appwrite
Version:
Easier Appwrite integration for your NestJS application.
134 lines (133 loc) • 7.24 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.createAppwriteAsyncProviders = createAppwriteAsyncProviders;
const common_1 = require("@nestjs/common");
const type_metadata_storage_1 = require("./storage/type-metadata.storage");
const appwrite_constants_1 = require("./appwrite.constants");
const node_appwrite_1 = require("node-appwrite");
const appwrite_database_repository_1 = require("appwrite-database-repository");
const attribute_status_enum_1 = require("./domain/attribute-status.enum");
const MAX_PAGE_SIZE = 5000;
const HALF_SECOND_IN_MS = 500;
const logger = new common_1.Logger('appwrite.providers');
const setupAttributes = async (option, repository, config) => {
const classProperties = type_metadata_storage_1.default.getClassProperties(option.class);
const currentAttributes = (await repository.getCollection()).attributes;
await Promise.all(classProperties.map(async (prop) => {
const { options } = prop;
try {
switch (prop.options.type) {
case String: {
if (options.enum) {
return await repository.createOrUpdateEnumAttribute(prop.propertyKey, Object.values(prop.options.enum), options.required, options.default, options.isArray);
}
if (options.isEmail) {
return await repository.createOrUpdateEmailAttribute(prop.propertyKey, options.required, options.default, options.isArray);
}
return await repository.createOrUpdateStringAttribute(prop.propertyKey, options.size, options.required, options.default, options.isArray, options.encrypt);
}
case Number: {
if (options.isInt) {
return await repository.createOrUpdateIntegerAttribute(prop.propertyKey, options.required, options.min, options.max, options.default);
}
return await repository.createOrUpdateFloatAttribute(prop.propertyKey, options.required, options.min, options.max, options.default);
}
case Boolean: {
return await repository.createOrUpdateBooleanAttribute(prop.propertyKey, options.required, options.default, options.isArray);
}
case Date: {
return await repository.createOrUpdateDateTimeAttribute(prop.propertyKey, options.required, options.default, options.isArray);
}
default: {
logger.warn(`Property type ${prop.options.type} is not a valid Appwrite property type`);
}
}
}
catch (err) {
logger.log(`Caught err for attribute ${prop.propertyKey} of type ${prop.options.type}}`);
logger.error(err);
}
return;
}));
if (!config.DELETE_REMOVED_ATTRIBUTES) {
return;
}
const collection = await repository.getCollection();
const collectionAttributes = collection.attributes;
const attributeKeys = classProperties.map(property => property.propertyKey);
const deletedAttributes = currentAttributes.filter(attribute => !attributeKeys.includes(attribute.key));
await Promise.all(deletedAttributes.map(async (attribute) => {
const attributeFound = collectionAttributes.find(collectionAttribute => attribute.key === collectionAttribute.key);
if (attributeFound?.status === attribute_status_enum_1.DatabaseResourceStatus.Processing) {
return;
}
try {
return await repository.deleteAttribute(attribute.key);
}
catch (err) {
logger.log(`Caught err when deleting attribute with key ${attribute.key}`);
logger.error(err);
}
}));
};
const waitForAttributesToBeCreated = async (client, config, collectionId) => {
const databases = new node_appwrite_1.Databases(client);
let attributesCreating = true;
while (attributesCreating) {
const attributes = (await databases.listAttributes(config.APPWRITE_DATABASE_ID, collectionId, [node_appwrite_1.Query.limit(MAX_PAGE_SIZE)])).attributes;
attributesCreating = attributes.some((attribute) => attribute.status === attribute_status_enum_1.DatabaseResourceStatus.Processing);
if (attributesCreating) {
logger.log('Waiting for attribute to be created');
await new Promise(resolve => setTimeout(resolve, HALF_SECOND_IN_MS));
}
}
};
const setupIndexes = async (option, repository) => {
const classIndexes = type_metadata_storage_1.default.getClassIndexes(option.class);
await Promise.all(classIndexes.map(async (index) => {
const { options } = index;
const indexName = `${index.propertyKey}-${options.type}`;
try {
let existingIndex = await repository.createOrUpdateIndex(indexName, options.type, options.attributes, options.orders);
let indexCreating = existingIndex.status === attribute_status_enum_1.DatabaseResourceStatus.Processing;
while (indexCreating) {
await new Promise(resolve => setTimeout(resolve, HALF_SECOND_IN_MS));
existingIndex = await repository.getIndex(indexName);
indexCreating = existingIndex.status === attribute_status_enum_1.DatabaseResourceStatus.Processing;
}
}
catch (err) {
logger.log(`Caught err for index ${indexName} of type ${options.type}}`);
logger.error(err);
}
}));
};
function createAppwriteAsyncProviders(modelFactories = []) {
return modelFactories.reduce((providers, option) => {
return [
...providers,
{
provide: option.class.name,
useFactory: async (client, config) => {
const { name } = option.class;
logger.debug(`Initializing schema: ${name}`);
const schemaMetaData = type_metadata_storage_1.default.getSchemaMetadata(option.class);
const repository = new appwrite_database_repository_1.AppwriteRepository(client, config.APPWRITE_DATABASE_ID, schemaMetaData.collectionId);
try {
await repository.createOrUpdateCollection(schemaMetaData.collectionName, schemaMetaData.permissions, schemaMetaData.documentSecurity, schemaMetaData.enabled);
}
catch (err) {
logger.log(`Caught err for update collection ${schemaMetaData.collectionName}`);
logger.error(err);
}
await setupAttributes(option, repository, config);
await waitForAttributesToBeCreated(client, config, schemaMetaData.collectionId);
await setupIndexes(option, repository);
logger.debug(`Done initializing schema: ${name}`);
return repository;
},
inject: [appwrite_constants_1.CLIENT_PROVIDER_NAME, appwrite_constants_1.CONFIG_PROVIDER_NAME, ...(option.inject || [])],
},
];
}, []);
}