@acodeninja/persist 1.0.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +47 -4
- package/exports/engine/http.js +3 -0
- package/package.json +2 -2
- package/src/Persist.js +5 -1
- package/src/Transactions.js +67 -0
- package/src/engine/Engine.js +51 -27
- package/src/engine/FileEngine.js +46 -16
- package/src/engine/HTTPEngine.js +181 -0
- package/src/engine/S3Engine.js +76 -60
- package/src/type/Model.js +1 -1
- package/src/type/complex/ArrayType.js +2 -2
- package/src/type/resolved/SlugType.js +1 -1
    
        package/README.md
    CHANGED
    
    | @@ -144,10 +144,10 @@ export class Tag extends Persist.Type.Model { | |
| 144 144 |  | 
| 145 145 | 
             
            const tag = new Tag({tag: 'documentation', description: 'How to use the persist library'});
         | 
| 146 146 |  | 
| 147 | 
            -
            FileEngine.find(Tag, {tag: 'documentation'});
         | 
| 147 | 
            +
            await FileEngine.find(Tag, {tag: 'documentation'});
         | 
| 148 148 | 
             
            // [Tag {tag: 'documentation', description: 'How to use the persist library'}]
         | 
| 149 149 |  | 
| 150 | 
            -
            FileEngine.search(Tag, 'how to');
         | 
| 150 | 
            +
            await FileEngine.search(Tag, 'how to');
         | 
| 151 151 | 
             
            // [Tag {tag: 'documentation', description: 'How to use the persist library'}]
         | 
| 152 152 | 
             
            ```
         | 
| 153 153 |  | 
| @@ -169,7 +169,26 @@ export class Tag extends Persist.Type.Model { | |
| 169 169 | 
             
              static tag = Persist.Type.String.required;
         | 
| 170 170 | 
             
            }
         | 
| 171 171 |  | 
| 172 | 
            -
            Persist.getEngine('local', FileEngine).put(new Tag({tag: 'documentation'}));
         | 
| 172 | 
            +
            await Persist.getEngine('local', FileEngine).put(new Tag({tag: 'documentation'}));
         | 
| 173 | 
            +
            ```
         | 
| 174 | 
            +
             | 
| 175 | 
            +
            ### HTTP Storage Engine
         | 
| 176 | 
            +
             | 
| 177 | 
            +
            To store models using an S3 Bucket, use the `S3` storage engine.
         | 
| 178 | 
            +
             | 
| 179 | 
            +
            ```javascript
         | 
| 180 | 
            +
            import Persist from "@acodeninja/persist";
         | 
| 181 | 
            +
            import HTTPEngine from "@acodeninja/persist/engine/http";
         | 
| 182 | 
            +
             | 
| 183 | 
            +
            Persist.addEngine('remote', HTTPEngine, {
         | 
| 184 | 
            +
              host: 'https://api.example.com',
         | 
| 185 | 
            +
            });
         | 
| 186 | 
            +
             | 
| 187 | 
            +
            export class Tag extends Persist.Type.Model {
         | 
| 188 | 
            +
              static tag = Persist.Type.String.required;
         | 
| 189 | 
            +
            }
         | 
| 190 | 
            +
             | 
| 191 | 
            +
            await Persist.getEngine('remote', HTTPEngine).put(new Tag({tag: 'documentation'}));
         | 
| 173 192 | 
             
            ```
         | 
| 174 193 |  | 
| 175 194 | 
             
            ### S3 Storage Engine
         | 
| @@ -189,5 +208,29 @@ export class Tag extends Persist.Type.Model { | |
| 189 208 | 
             
              static tag = Persist.Type.String.required;
         | 
| 190 209 | 
             
            }
         | 
| 191 210 |  | 
| 192 | 
            -
            Persist.getEngine('remote', S3Engine).put(new Tag({tag: 'documentation'}));
         | 
| 211 | 
            +
            await Persist.getEngine('remote', S3Engine).put(new Tag({tag: 'documentation'}));
         | 
| 212 | 
            +
            ```
         | 
| 213 | 
            +
             | 
| 214 | 
            +
            ## Transactions
         | 
| 215 | 
            +
             | 
| 216 | 
            +
            Create transactions to automatically roll back on failure to update.
         | 
| 217 | 
            +
             | 
| 218 | 
            +
            ```javascript
         | 
| 219 | 
            +
            import Persist from "@acodeninja/persist";
         | 
| 220 | 
            +
            import S3Engine from "@acodeninja/persist/engine/s3";
         | 
| 221 | 
            +
             | 
| 222 | 
            +
            Persist.addEngine('remote', S3Engine, {
         | 
| 223 | 
            +
              bucket: 'test-bucket',
         | 
| 224 | 
            +
              client: new S3Client(),
         | 
| 225 | 
            +
              transactions: true,
         | 
| 226 | 
            +
            });
         | 
| 227 | 
            +
             | 
| 228 | 
            +
            export class Tag extends Persist.Type.Model {
         | 
| 229 | 
            +
              static tag = Persist.Type.String.required;
         | 
| 230 | 
            +
            }
         | 
| 231 | 
            +
             | 
| 232 | 
            +
            const transaction = Persist.getEngine('remote', S3Engine).start();
         | 
| 233 | 
            +
             | 
| 234 | 
            +
            await transaction.put(new Tag({tag: 'documentation'}));
         | 
| 235 | 
            +
            await transaction.commit();
         | 
| 193 236 | 
             
            ```
         | 
    
        package/package.json
    CHANGED
    
    | @@ -1,6 +1,6 @@ | |
| 1 1 | 
             
            {
         | 
| 2 2 | 
             
              "name": "@acodeninja/persist",
         | 
| 3 | 
            -
              "version": " | 
| 3 | 
            +
              "version": "2.0.0",
         | 
| 4 4 | 
             
              "description": "A JSON based data modelling and persistence module with alternate storage mechanisms.",
         | 
| 5 5 | 
             
              "type": "module",
         | 
| 6 6 | 
             
              "scripts": {
         | 
| @@ -43,7 +43,7 @@ | |
| 43 43 | 
             
                "globals": "^15.8.0",
         | 
| 44 44 | 
             
                "husky": "^9.1.1",
         | 
| 45 45 | 
             
                "lodash": "^4.17.21",
         | 
| 46 | 
            -
                "monocart-coverage-reports": "^2. | 
| 46 | 
            +
                "monocart-coverage-reports": "^2.10.2",
         | 
| 47 47 | 
             
                "semantic-release": "^24.0.0",
         | 
| 48 48 | 
             
                "sinon": "^18.0.0"
         | 
| 49 49 | 
             
              }
         | 
    
        package/src/Persist.js
    CHANGED
    
    | @@ -1,4 +1,5 @@ | |
| 1 1 | 
             
            import Type from '../src/type/index.js';
         | 
| 2 | 
            +
            import enableTransactions from './Transactions.js';
         | 
| 2 3 |  | 
| 3 4 | 
             
            /**
         | 
| 4 5 | 
             
             * @class Persist
         | 
| @@ -35,6 +36,9 @@ export default class Persist { | |
| 35 36 | 
             
                static addEngine(group, engine, configuration) {
         | 
| 36 37 | 
             
                    if (!this._engine[group]) this._engine[group] = {};
         | 
| 37 38 |  | 
| 38 | 
            -
                    this._engine[group][engine.name] = | 
| 39 | 
            +
                    this._engine[group][engine.name] =
         | 
| 40 | 
            +
                        configuration.transactions ?
         | 
| 41 | 
            +
                            enableTransactions(engine.configure(configuration)) :
         | 
| 42 | 
            +
                            engine.configure(configuration);
         | 
| 39 43 | 
             
                }
         | 
| 40 44 | 
             
            }
         | 
| @@ -0,0 +1,67 @@ | |
| 1 | 
            +
            class TransactionError extends Error {
         | 
| 2 | 
            +
            }
         | 
| 3 | 
            +
             | 
| 4 | 
            +
            export class TransactionCommittedError extends TransactionError {
         | 
| 5 | 
            +
                message = 'Transaction was already committed.';
         | 
| 6 | 
            +
            }
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            export default function enableTransactions(engine) {
         | 
| 9 | 
            +
                class TransactionalEngine extends engine {
         | 
| 10 | 
            +
                }
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                TransactionalEngine.start = () => {
         | 
| 13 | 
            +
                    class Transaction extends TransactionalEngine {
         | 
| 14 | 
            +
                        static transactions = [];
         | 
| 15 | 
            +
                        static committed = false;
         | 
| 16 | 
            +
                        static failed = false;
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                        static async put(model) {
         | 
| 19 | 
            +
                            this.transactions.push({
         | 
| 20 | 
            +
                                hasRun: false,
         | 
| 21 | 
            +
                                hasRolledBack: false,
         | 
| 22 | 
            +
                                model,
         | 
| 23 | 
            +
                            });
         | 
| 24 | 
            +
                        }
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                        static _checkCommitted() {
         | 
| 27 | 
            +
                            if (this.committed) throw new TransactionCommittedError();
         | 
| 28 | 
            +
                        }
         | 
| 29 | 
            +
             | 
| 30 | 
            +
                        static async commit() {
         | 
| 31 | 
            +
                            this._checkCommitted();
         | 
| 32 | 
            +
             | 
| 33 | 
            +
                            try {
         | 
| 34 | 
            +
                                for (const [index, {model}] of this.transactions.entries()) {
         | 
| 35 | 
            +
                                    try {
         | 
| 36 | 
            +
                                        this.transactions[index].original = await engine.get(model.constructor, model.id);
         | 
| 37 | 
            +
                                    } catch (_) {
         | 
| 38 | 
            +
                                        this.transactions[index].original = null;
         | 
| 39 | 
            +
                                    }
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                                    await engine.put(model);
         | 
| 42 | 
            +
                                    this.transactions[index].hasRun = true;
         | 
| 43 | 
            +
                                }
         | 
| 44 | 
            +
                            } catch (e) {
         | 
| 45 | 
            +
                                this.committed = true;
         | 
| 46 | 
            +
                                this.failed = true;
         | 
| 47 | 
            +
                                for (const [index, {original}] of this.transactions.entries()) {
         | 
| 48 | 
            +
                                    if (original) {
         | 
| 49 | 
            +
                                        await engine.put(this.transactions[index].original);
         | 
| 50 | 
            +
                                    }
         | 
| 51 | 
            +
                                    this.transactions[index].hasRolledBack = true;
         | 
| 52 | 
            +
                                }
         | 
| 53 | 
            +
                                throw e;
         | 
| 54 | 
            +
                            }
         | 
| 55 | 
            +
             | 
| 56 | 
            +
                            this.committed = true;
         | 
| 57 | 
            +
                            this.failed = false;
         | 
| 58 | 
            +
                        }
         | 
| 59 | 
            +
                    }
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                    return Transaction;
         | 
| 62 | 
            +
                };
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                Object.defineProperty(TransactionalEngine, 'name', {value: `${engine.toString()}`});
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                return TransactionalEngine;
         | 
| 67 | 
            +
            }
         | 
    
        package/src/engine/Engine.js
    CHANGED
    
    | @@ -40,33 +40,35 @@ export default class Engine { | |
| 40 40 | 
             
                }
         | 
| 41 41 |  | 
| 42 42 | 
             
                static async search(model, query) {
         | 
| 43 | 
            -
                     | 
| 43 | 
            +
                    this.checkConfiguration();
         | 
| 44 44 |  | 
| 45 | 
            -
                     | 
| 46 | 
            -
                         | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
            -
                        for (const result of results) {
         | 
| 51 | 
            -
                            output.push({
         | 
| 52 | 
            -
                                ...result,
         | 
| 53 | 
            -
                                model: await this.get(model, result.ref),
         | 
| 54 | 
            -
                            });
         | 
| 55 | 
            -
                        }
         | 
| 45 | 
            +
                    const index = await this.getSearchIndexCompiled(model).catch(() => {
         | 
| 46 | 
            +
                        throw new EngineError(`The model ${model.toString()} does not have a search index available.`);
         | 
| 47 | 
            +
                    });
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                    const searchIndex = lunr.Index.load(index);
         | 
| 56 50 |  | 
| 57 | 
            -
             | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 51 | 
            +
                    const results = searchIndex.search(`*${query}*`);
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                    const output = [];
         | 
| 54 | 
            +
                    for (const result of results) {
         | 
| 55 | 
            +
                        output.push({
         | 
| 56 | 
            +
                            ...result,
         | 
| 57 | 
            +
                            model: await this.get(model, result.ref),
         | 
| 58 | 
            +
                        });
         | 
| 60 59 | 
             
                    }
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                    return output;
         | 
| 61 62 | 
             
                }
         | 
| 62 63 |  | 
| 63 64 | 
             
                static async find(model, parameters) {
         | 
| 65 | 
            +
                    this.checkConfiguration();
         | 
| 64 66 | 
             
                    const response = await this.findByValue(model, parameters);
         | 
| 65 | 
            -
             | 
| 66 67 | 
             
                    return response.map(m => model.fromData(m));
         | 
| 67 68 | 
             
                }
         | 
| 68 69 |  | 
| 69 70 | 
             
                static async put(model) {
         | 
| 71 | 
            +
                    this.checkConfiguration();
         | 
| 70 72 | 
             
                    const uploadedModels = [];
         | 
| 71 73 | 
             
                    const indexUpdates = {};
         | 
| 72 74 |  | 
| @@ -74,9 +76,9 @@ export default class Engine { | |
| 74 76 | 
             
                        if (uploadedModels.includes(model.id)) return false;
         | 
| 75 77 | 
             
                        model.validate();
         | 
| 76 78 |  | 
| 77 | 
            -
                        uploadedModels.push(model.id);
         | 
| 78 | 
            -
             | 
| 79 79 | 
             
                        await this.putModel(model);
         | 
| 80 | 
            +
             | 
| 81 | 
            +
                        uploadedModels.push(model.id);
         | 
| 80 82 | 
             
                        indexUpdates[model.constructor.name] = (indexUpdates[model.constructor.name] ?? []).concat([model]);
         | 
| 81 83 |  | 
| 82 84 | 
             
                        if (model.constructor.searchProperties().length > 0) {
         | 
| @@ -84,10 +86,11 @@ export default class Engine { | |
| 84 86 | 
             
                                ...await this.getSearchIndexRaw(model.constructor),
         | 
| 85 87 | 
             
                                [model.id]: model.toSearchData(),
         | 
| 86 88 | 
             
                            };
         | 
| 89 | 
            +
             | 
| 87 90 | 
             
                            await this.putSearchIndexRaw(model.constructor, rawSearchIndex);
         | 
| 88 91 |  | 
| 89 92 | 
             
                            const compiledIndex = lunr(function () {
         | 
| 90 | 
            -
                                this.ref('id')
         | 
| 93 | 
            +
                                this.ref('id');
         | 
| 91 94 |  | 
| 92 95 | 
             
                                for (const field of model.constructor.searchProperties()) {
         | 
| 93 96 | 
             
                                    this.field(field);
         | 
| @@ -95,7 +98,7 @@ export default class Engine { | |
| 95 98 |  | 
| 96 99 | 
             
                                Object.values(rawSearchIndex).forEach(function (doc) {
         | 
| 97 100 | 
             
                                    this.add(doc);
         | 
| 98 | 
            -
                                }, this)
         | 
| 101 | 
            +
                                }, this);
         | 
| 99 102 | 
             
                            });
         | 
| 100 103 |  | 
| 101 104 | 
             
                            await this.putSearchIndexCompiled(model.constructor, compiledIndex);
         | 
| @@ -118,16 +121,19 @@ export default class Engine { | |
| 118 121 | 
             
                }
         | 
| 119 122 |  | 
| 120 123 | 
             
                static async get(model, id) {
         | 
| 121 | 
            -
                     | 
| 124 | 
            +
                    this.checkConfiguration();
         | 
| 122 125 |  | 
| 123 126 | 
             
                    try {
         | 
| 127 | 
            +
                        const found = await this.getById(id);
         | 
| 124 128 | 
             
                        return model.fromData(found);
         | 
| 125 | 
            -
                    } catch ( | 
| 126 | 
            -
                         | 
| 129 | 
            +
                    } catch (error) {
         | 
| 130 | 
            +
                        if (error.constructor === NotImplementedError) throw error;
         | 
| 131 | 
            +
                        throw new NotFoundEngineError(`${this.name}.get(${id}) model not found`, error);
         | 
| 127 132 | 
             
                    }
         | 
| 128 133 | 
             
                }
         | 
| 129 134 |  | 
| 130 135 | 
             
                static async hydrate(model) {
         | 
| 136 | 
            +
                    this.checkConfiguration();
         | 
| 131 137 | 
             
                    const hydratedModels = {};
         | 
| 132 138 |  | 
| 133 139 | 
             
                    const hydrateModel = async (modelToProcess) => {
         | 
| @@ -142,7 +148,7 @@ export default class Engine { | |
| 142 148 | 
             
                        }
         | 
| 143 149 |  | 
| 144 150 | 
             
                        return modelToProcess;
         | 
| 145 | 
            -
                    }
         | 
| 151 | 
            +
                    };
         | 
| 146 152 |  | 
| 147 153 | 
             
                    const hydrateSubModel = async (property, modelToProcess, name) => {
         | 
| 148 154 | 
             
                        if (hydratedModels[property.id]) {
         | 
| @@ -155,7 +161,7 @@ export default class Engine { | |
| 155 161 | 
             
                        const hydratedSubModel = await hydrateModel(subModel);
         | 
| 156 162 | 
             
                        hydratedModels[property.id] = hydratedSubModel;
         | 
| 157 163 | 
             
                        return hydratedSubModel;
         | 
| 158 | 
            -
                    }
         | 
| 164 | 
            +
                    };
         | 
| 159 165 |  | 
| 160 166 | 
             
                    const hydrateModelList = async (property, modelToProcess, name) => {
         | 
| 161 167 | 
             
                        const subModelClass = getSubModelClass(modelToProcess, name, true);
         | 
| @@ -177,7 +183,7 @@ export default class Engine { | |
| 177 183 | 
             
                            hydratedModels[hydratedSubModel.id] = hydratedSubModel;
         | 
| 178 184 | 
             
                            return hydratedSubModel;
         | 
| 179 185 | 
             
                        }));
         | 
| 180 | 
            -
                    }
         | 
| 186 | 
            +
                    };
         | 
| 181 187 |  | 
| 182 188 | 
             
                    function getSubModelClass(modelToProcess, name, isArray = false) {
         | 
| 183 189 | 
             
                        const constructorField = modelToProcess.constructor[name];
         | 
| @@ -195,17 +201,26 @@ export default class Engine { | |
| 195 201 | 
             
                        static _configuration = configuration;
         | 
| 196 202 | 
             
                    }
         | 
| 197 203 |  | 
| 198 | 
            -
                    Object.defineProperty(ConfiguredStore, 'name', {value: `${this.toString()}`})
         | 
| 204 | 
            +
                    Object.defineProperty(ConfiguredStore, 'name', {value: `${this.toString()}`});
         | 
| 199 205 |  | 
| 200 206 | 
             
                    return ConfiguredStore;
         | 
| 201 207 | 
             
                }
         | 
| 202 208 |  | 
| 209 | 
            +
                static checkConfiguration() {
         | 
| 210 | 
            +
             | 
| 211 | 
            +
                }
         | 
| 212 | 
            +
             | 
| 203 213 | 
             
                static toString() {
         | 
| 204 214 | 
             
                    return this.name;
         | 
| 205 215 | 
             
                }
         | 
| 206 216 | 
             
            };
         | 
| 207 217 |  | 
| 208 218 | 
             
            export class EngineError extends Error {
         | 
| 219 | 
            +
                underlyingError;
         | 
| 220 | 
            +
                constructor(message, error = undefined) {
         | 
| 221 | 
            +
                    super(message);
         | 
| 222 | 
            +
                    this.underlyingError = error;
         | 
| 223 | 
            +
                }
         | 
| 209 224 | 
             
            }
         | 
| 210 225 |  | 
| 211 226 | 
             
            export class NotFoundEngineError extends EngineError {
         | 
| @@ -213,3 +228,12 @@ export class NotFoundEngineError extends EngineError { | |
| 213 228 |  | 
| 214 229 | 
             
            export class NotImplementedError extends EngineError {
         | 
| 215 230 | 
             
            }
         | 
| 231 | 
            +
             | 
| 232 | 
            +
            export class MissConfiguredError extends EngineError {
         | 
| 233 | 
            +
                configuration;
         | 
| 234 | 
            +
             | 
| 235 | 
            +
                constructor(configuration) {
         | 
| 236 | 
            +
                    super('Engine is miss-configured');
         | 
| 237 | 
            +
                    this.configuration = configuration;
         | 
| 238 | 
            +
                }
         | 
| 239 | 
            +
            }
         | 
    
        package/src/engine/FileEngine.js
    CHANGED
    
    | @@ -1,7 +1,11 @@ | |
| 1 | 
            +
            import Engine, {EngineError, MissConfiguredError} from './Engine.js';
         | 
| 1 2 | 
             
            import {dirname, join} from 'node:path';
         | 
| 2 | 
            -
            import Engine from './Engine.js';
         | 
| 3 3 | 
             
            import fs from 'node:fs/promises';
         | 
| 4 4 |  | 
| 5 | 
            +
            class FileEngineError extends EngineError {}
         | 
| 6 | 
            +
             | 
| 7 | 
            +
            class FailedWriteFileEngineError extends FileEngineError {}
         | 
| 8 | 
            +
             | 
| 5 9 | 
             
            /**
         | 
| 6 10 | 
             
             * @class FileEngine
         | 
| 7 11 | 
             
             * @extends Engine
         | 
| @@ -14,18 +18,22 @@ export default class FileEngine extends Engine { | |
| 14 18 | 
             
                    return super.configure(configuration);
         | 
| 15 19 | 
             
                }
         | 
| 16 20 |  | 
| 21 | 
            +
                static checkConfiguration() {
         | 
| 22 | 
            +
                    if (
         | 
| 23 | 
            +
                        !this._configuration?.path ||
         | 
| 24 | 
            +
                        !this._configuration?.filesystem
         | 
| 25 | 
            +
                    ) throw new MissConfiguredError(this._configuration);
         | 
| 26 | 
            +
                }
         | 
| 27 | 
            +
             | 
| 17 28 | 
             
                static async getById(id) {
         | 
| 18 29 | 
             
                    const filePath = join(this._configuration.path, `${id}.json`);
         | 
| 19 30 |  | 
| 20 | 
            -
                     | 
| 21 | 
            -
                        return JSON.parse(await this._configuration.filesystem.readFile(filePath).then(f => f.toString()));
         | 
| 22 | 
            -
                    } catch (_) {
         | 
| 23 | 
            -
                        return null;
         | 
| 24 | 
            -
                    }
         | 
| 31 | 
            +
                    return JSON.parse(await this._configuration.filesystem.readFile(filePath).then(f => f.toString()));
         | 
| 25 32 | 
             
                }
         | 
| 26 33 |  | 
| 27 34 | 
             
                static async findByValue(model, parameters) {
         | 
| 28 35 | 
             
                    const index = JSON.parse((await this._configuration.filesystem.readFile(join(this._configuration.path, model.name, '_index.json')).catch(() => '{}')).toString());
         | 
| 36 | 
            +
             | 
| 29 37 | 
             
                    return Object.values(index)
         | 
| 30 38 | 
             
                        .filter((model) =>
         | 
| 31 39 | 
             
                            Object.entries(parameters)
         | 
| @@ -36,8 +44,12 @@ export default class FileEngine extends Engine { | |
| 36 44 | 
             
                static async putModel(model) {
         | 
| 37 45 | 
             
                    const filePath = join(this._configuration.path, `${model.id}.json`);
         | 
| 38 46 |  | 
| 39 | 
            -
                     | 
| 40 | 
            -
             | 
| 47 | 
            +
                    try {
         | 
| 48 | 
            +
                        await this._configuration.filesystem.mkdir(dirname(filePath), {recursive: true});
         | 
| 49 | 
            +
                        await this._configuration.filesystem.writeFile(filePath, JSON.stringify(model.toData()));
         | 
| 50 | 
            +
                    } catch (error) {
         | 
| 51 | 
            +
                        throw new FailedWriteFileEngineError(`Failed to put file://${filePath}`, error);
         | 
| 52 | 
            +
                    }
         | 
| 41 53 | 
             
                }
         | 
| 42 54 |  | 
| 43 55 | 
             
                static async putIndex(index) {
         | 
| @@ -46,10 +58,14 @@ export default class FileEngine extends Engine { | |
| 46 58 | 
             
                        const filePath = join(this._configuration.path, location, '_index.json');
         | 
| 47 59 | 
             
                        const currentIndex = JSON.parse((await this._configuration.filesystem.readFile(filePath).catch(() => '{}')).toString());
         | 
| 48 60 |  | 
| 49 | 
            -
                         | 
| 50 | 
            -
                             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 61 | 
            +
                        try {
         | 
| 62 | 
            +
                            await this._configuration.filesystem.writeFile(filePath, JSON.stringify({
         | 
| 63 | 
            +
                                ...currentIndex,
         | 
| 64 | 
            +
                                ...modelIndex,
         | 
| 65 | 
            +
                            }));
         | 
| 66 | 
            +
                        } catch (error) {
         | 
| 67 | 
            +
                            throw new FailedWriteFileEngineError(`Failed to put file://${filePath}`, error);
         | 
| 68 | 
            +
                        }
         | 
| 53 69 | 
             
                    };
         | 
| 54 70 |  | 
| 55 71 | 
             
                    for (const [location, models] of Object.entries(index)) {
         | 
| @@ -60,20 +76,34 @@ export default class FileEngine extends Engine { | |
| 60 76 | 
             
                }
         | 
| 61 77 |  | 
| 62 78 | 
             
                static async getSearchIndexCompiled(model) {
         | 
| 63 | 
            -
                    return  | 
| 79 | 
            +
                    return await this._configuration.filesystem.readFile(join(this._configuration.path, model.name, '_search_index.json'))
         | 
| 80 | 
            +
                        .then(b => b.toString())
         | 
| 81 | 
            +
                        .then(JSON.parse);
         | 
| 64 82 | 
             
                }
         | 
| 65 83 |  | 
| 66 84 | 
             
                static async getSearchIndexRaw(model) {
         | 
| 67 | 
            -
                    return  | 
| 85 | 
            +
                    return await this._configuration.filesystem.readFile(join(this._configuration.path, model.name, '_search_index_raw.json'))
         | 
| 86 | 
            +
                        .then(b => b.toString())
         | 
| 87 | 
            +
                        .then(JSON.parse)
         | 
| 88 | 
            +
                        .catch(() => ({}));
         | 
| 68 89 | 
             
                }
         | 
| 69 90 |  | 
| 70 91 | 
             
                static async putSearchIndexCompiled(model, compiledIndex) {
         | 
| 71 92 | 
             
                    const filePath = join(this._configuration.path, model.name, '_search_index.json');
         | 
| 72 | 
            -
             | 
| 93 | 
            +
             | 
| 94 | 
            +
                    try {
         | 
| 95 | 
            +
                        await this._configuration.filesystem.writeFile(filePath, JSON.stringify(compiledIndex));
         | 
| 96 | 
            +
                    } catch (error) {
         | 
| 97 | 
            +
                        throw new FailedWriteFileEngineError(`Failed to put file://${filePath}`, error);
         | 
| 98 | 
            +
                    }
         | 
| 73 99 | 
             
                }
         | 
| 74 100 |  | 
| 75 101 | 
             
                static async putSearchIndexRaw(model, rawIndex) {
         | 
| 76 102 | 
             
                    const filePath = join(this._configuration.path, model.name, '_search_index_raw.json');
         | 
| 77 | 
            -
                     | 
| 103 | 
            +
                    try {
         | 
| 104 | 
            +
                        await this._configuration.filesystem.writeFile(filePath, JSON.stringify(rawIndex));
         | 
| 105 | 
            +
                    } catch (error) {
         | 
| 106 | 
            +
                        throw new FailedWriteFileEngineError(`Failed to put file://${filePath}`, error);
         | 
| 107 | 
            +
                    }
         | 
| 78 108 | 
             
                }
         | 
| 79 109 | 
             
            }
         | 
| @@ -0,0 +1,181 @@ | |
| 1 | 
            +
            import Engine, {EngineError, MissConfiguredError} from './Engine.js';
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            export class HTTPEngineError extends EngineError {
         | 
| 4 | 
            +
            }
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            export class HTTPRequestFailedError extends HTTPEngineError {
         | 
| 7 | 
            +
                constructor(url, options, response) {
         | 
| 8 | 
            +
                    const method = options.method?.toLowerCase() || 'get';
         | 
| 9 | 
            +
                    super(`Failed to ${method} ${url}`);
         | 
| 10 | 
            +
                    this.response = response;
         | 
| 11 | 
            +
                    this.url = url;
         | 
| 12 | 
            +
                    this.options = options;
         | 
| 13 | 
            +
                }
         | 
| 14 | 
            +
            }
         | 
| 15 | 
            +
             | 
| 16 | 
            +
            export default class HTTPEngine extends Engine {
         | 
| 17 | 
            +
                static configure(configuration = {}) {
         | 
| 18 | 
            +
                    configuration.fetchOptions = {
         | 
| 19 | 
            +
                        ...(configuration.fetchOptions ?? {}),
         | 
| 20 | 
            +
                        headers: {
         | 
| 21 | 
            +
                            ...(configuration.fetchOptions?.headers ?? {}),
         | 
| 22 | 
            +
                            Accept: 'application/json',
         | 
| 23 | 
            +
                        },
         | 
| 24 | 
            +
                    };
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                    return super.configure(configuration);
         | 
| 27 | 
            +
                }
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                static checkConfiguration() {
         | 
| 30 | 
            +
                    if (
         | 
| 31 | 
            +
                        !this._configuration?.host
         | 
| 32 | 
            +
                    ) throw new MissConfiguredError(this._configuration);
         | 
| 33 | 
            +
                }
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                static _getReadOptions() {
         | 
| 36 | 
            +
                    return this._configuration.fetchOptions;
         | 
| 37 | 
            +
                }
         | 
| 38 | 
            +
             | 
| 39 | 
            +
                static _getWriteOptions() {
         | 
| 40 | 
            +
                    return {
         | 
| 41 | 
            +
                        ...this._getReadOptions(),
         | 
| 42 | 
            +
                        headers: {
         | 
| 43 | 
            +
                            ...this._getReadOptions().headers,
         | 
| 44 | 
            +
                            'Content-Type': 'application/json',
         | 
| 45 | 
            +
                        },
         | 
| 46 | 
            +
                        method: 'PUT',
         | 
| 47 | 
            +
                    };
         | 
| 48 | 
            +
                }
         | 
| 49 | 
            +
             | 
| 50 | 
            +
                static async _processFetch(url, options, defaultValue = undefined) {
         | 
| 51 | 
            +
                    return this._configuration.fetch(url, options)
         | 
| 52 | 
            +
                        .then(response => {
         | 
| 53 | 
            +
                            if (!response.ok) {
         | 
| 54 | 
            +
                                if (defaultValue !== undefined) {
         | 
| 55 | 
            +
                                    return {json: () => Promise.resolve(defaultValue)};
         | 
| 56 | 
            +
                                }
         | 
| 57 | 
            +
             | 
| 58 | 
            +
                                throw new HTTPRequestFailedError(url, options, response);
         | 
| 59 | 
            +
                            }
         | 
| 60 | 
            +
             | 
| 61 | 
            +
                            return response;
         | 
| 62 | 
            +
                        })
         | 
| 63 | 
            +
                        .then(r => r.json());
         | 
| 64 | 
            +
                }
         | 
| 65 | 
            +
             | 
| 66 | 
            +
                static async getById(id) {
         | 
| 67 | 
            +
                    this.checkConfiguration();
         | 
| 68 | 
            +
             | 
| 69 | 
            +
                    const url = new URL([
         | 
| 70 | 
            +
                        this._configuration.host,
         | 
| 71 | 
            +
                        this._configuration.prefix,
         | 
| 72 | 
            +
                        `${id}.json`,
         | 
| 73 | 
            +
                    ].filter(e => !!e).join('/'));
         | 
| 74 | 
            +
             | 
| 75 | 
            +
                    return await this._processFetch(url, this._getReadOptions());
         | 
| 76 | 
            +
                }
         | 
| 77 | 
            +
             | 
| 78 | 
            +
                static async findByValue(model, parameters) {
         | 
| 79 | 
            +
                    const index = await this.getIndex(model.name);
         | 
| 80 | 
            +
                    return Object.values(index)
         | 
| 81 | 
            +
                        .filter((model) =>
         | 
| 82 | 
            +
                            Object.entries(parameters)
         | 
| 83 | 
            +
                                .some(([name, value]) => model[name] === value),
         | 
| 84 | 
            +
                        );
         | 
| 85 | 
            +
                }
         | 
| 86 | 
            +
             | 
| 87 | 
            +
                static async putModel(model) {
         | 
| 88 | 
            +
                    const url = new URL([
         | 
| 89 | 
            +
                        this._configuration.host,
         | 
| 90 | 
            +
                        this._configuration.prefix,
         | 
| 91 | 
            +
                        `${model.id}.json`,
         | 
| 92 | 
            +
                    ].filter(e => !!e).join('/'));
         | 
| 93 | 
            +
             | 
| 94 | 
            +
                    return await this._processFetch(url, {
         | 
| 95 | 
            +
                        ...this._getWriteOptions(),
         | 
| 96 | 
            +
                        body: JSON.stringify(model.toData()),
         | 
| 97 | 
            +
                    });
         | 
| 98 | 
            +
                }
         | 
| 99 | 
            +
             | 
| 100 | 
            +
                static async putIndex(index) {
         | 
| 101 | 
            +
                    const processIndex = async (location, models) => {
         | 
| 102 | 
            +
                        const modelIndex = Object.fromEntries(models.map(m => [m.id, m.toIndexData()]));
         | 
| 103 | 
            +
                        const url = new URL([
         | 
| 104 | 
            +
                            this._configuration.host,
         | 
| 105 | 
            +
                            this._configuration.prefix,
         | 
| 106 | 
            +
                            location,
         | 
| 107 | 
            +
                            '_index.json',
         | 
| 108 | 
            +
                        ].filter(e => !!e).join('/'));
         | 
| 109 | 
            +
             | 
| 110 | 
            +
                        return await this._processFetch(url, {
         | 
| 111 | 
            +
                            ...this._getWriteOptions(),
         | 
| 112 | 
            +
                            body: JSON.stringify({
         | 
| 113 | 
            +
                                ...await this.getIndex(location),
         | 
| 114 | 
            +
                                ...modelIndex,
         | 
| 115 | 
            +
                            }),
         | 
| 116 | 
            +
                        });
         | 
| 117 | 
            +
                    };
         | 
| 118 | 
            +
             | 
| 119 | 
            +
                    for (const [location, models] of Object.entries(index)) {
         | 
| 120 | 
            +
                        await processIndex(location, models);
         | 
| 121 | 
            +
                    }
         | 
| 122 | 
            +
             | 
| 123 | 
            +
                    await processIndex(null, Object.values(index).flat());
         | 
| 124 | 
            +
                }
         | 
| 125 | 
            +
             | 
| 126 | 
            +
                static async getIndex(location) {
         | 
| 127 | 
            +
                    const url = new URL([this._configuration.host, this._configuration.prefix, location, '_index.json'].filter(e => !!e).join('/'));
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                    return await this._processFetch(url, this._getReadOptions(), {});
         | 
| 130 | 
            +
                }
         | 
| 131 | 
            +
             | 
| 132 | 
            +
                static async getSearchIndexCompiled(model) {
         | 
| 133 | 
            +
                    const url = new URL([
         | 
| 134 | 
            +
                        this._configuration.host,
         | 
| 135 | 
            +
                        this._configuration.prefix,
         | 
| 136 | 
            +
                        model.toString(),
         | 
| 137 | 
            +
                        '_search_index.json',
         | 
| 138 | 
            +
                    ].join('/'));
         | 
| 139 | 
            +
             | 
| 140 | 
            +
                    return await this._processFetch(url, this._getReadOptions());
         | 
| 141 | 
            +
                }
         | 
| 142 | 
            +
             | 
| 143 | 
            +
                static async getSearchIndexRaw(model) {
         | 
| 144 | 
            +
                    const url = new URL([
         | 
| 145 | 
            +
                        this._configuration.host,
         | 
| 146 | 
            +
                        this._configuration.prefix,
         | 
| 147 | 
            +
                        model.toString(),
         | 
| 148 | 
            +
                        '_search_index_raw.json',
         | 
| 149 | 
            +
                    ].join('/'));
         | 
| 150 | 
            +
             | 
| 151 | 
            +
                    return await this._processFetch(url, this._getReadOptions()).catch(() => ({}));
         | 
| 152 | 
            +
                }
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                static async putSearchIndexCompiled(model, compiledIndex) {
         | 
| 155 | 
            +
                    const url = new URL([
         | 
| 156 | 
            +
                        this._configuration.host,
         | 
| 157 | 
            +
                        this._configuration.prefix,
         | 
| 158 | 
            +
                        model.name,
         | 
| 159 | 
            +
                        '_search_index.json',
         | 
| 160 | 
            +
                    ].filter(e => !!e).join('/'));
         | 
| 161 | 
            +
             | 
| 162 | 
            +
                    return this._processFetch(url, {
         | 
| 163 | 
            +
                        ...this._getWriteOptions(),
         | 
| 164 | 
            +
                        body: JSON.stringify(compiledIndex),
         | 
| 165 | 
            +
                    });
         | 
| 166 | 
            +
                }
         | 
| 167 | 
            +
             | 
| 168 | 
            +
                static async putSearchIndexRaw(model, rawIndex) {
         | 
| 169 | 
            +
                    const url = new URL([
         | 
| 170 | 
            +
                        this._configuration.host,
         | 
| 171 | 
            +
                        this._configuration.prefix,
         | 
| 172 | 
            +
                        model.name,
         | 
| 173 | 
            +
                        '_search_index_raw.json',
         | 
| 174 | 
            +
                    ].filter(e => !!e).join('/'));
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                    return await this._processFetch(url, {
         | 
| 177 | 
            +
                        ...this._getWriteOptions(),
         | 
| 178 | 
            +
                        body: JSON.stringify(rawIndex),
         | 
| 179 | 
            +
                    });
         | 
| 180 | 
            +
                }
         | 
| 181 | 
            +
            }
         | 
    
        package/src/engine/S3Engine.js
    CHANGED
    
    | @@ -1,23 +1,32 @@ | |
| 1 | 
            +
            import Engine, {EngineError, MissConfiguredError} from './Engine.js';
         | 
| 1 2 | 
             
            import {GetObjectCommand, PutObjectCommand} from '@aws-sdk/client-s3';
         | 
| 2 | 
            -
             | 
| 3 | 
            +
             | 
| 4 | 
            +
            class S3EngineError extends EngineError {}
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            class FailedPutS3EngineError extends S3EngineError {}
         | 
| 3 7 |  | 
| 4 8 | 
             
            export default class S3Engine extends Engine {
         | 
| 9 | 
            +
                static checkConfiguration() {
         | 
| 10 | 
            +
                    if (
         | 
| 11 | 
            +
                        !this._configuration?.bucket ||
         | 
| 12 | 
            +
                        !this._configuration?.client
         | 
| 13 | 
            +
                    ) throw new MissConfiguredError(this._configuration);
         | 
| 14 | 
            +
                }
         | 
| 15 | 
            +
             | 
| 5 16 | 
             
                static async getById(id) {
         | 
| 6 17 | 
             
                    const objectPath = [this._configuration.prefix, `${id}.json`].join('/');
         | 
| 7 18 |  | 
| 8 | 
            -
                     | 
| 9 | 
            -
                         | 
| 10 | 
            -
             | 
| 11 | 
            -
             | 
| 12 | 
            -
             | 
| 13 | 
            -
             | 
| 14 | 
            -
                    } catch (_error) {
         | 
| 15 | 
            -
                        return undefined;
         | 
| 16 | 
            -
                    }
         | 
| 19 | 
            +
                    const data = await this._configuration.client.send(new GetObjectCommand({
         | 
| 20 | 
            +
                        Bucket: this._configuration.bucket,
         | 
| 21 | 
            +
                        Key: objectPath,
         | 
| 22 | 
            +
                    }));
         | 
| 23 | 
            +
             | 
| 24 | 
            +
                    return JSON.parse(await data.Body.transformToString());
         | 
| 17 25 | 
             
                }
         | 
| 18 26 |  | 
| 19 27 | 
             
                static async findByValue(model, parameters) {
         | 
| 20 28 | 
             
                    const index = await this.getIndex(model.name);
         | 
| 29 | 
            +
             | 
| 21 30 | 
             
                    return Object.values(index)
         | 
| 22 31 | 
             
                        .filter((model) =>
         | 
| 23 32 | 
             
                            Object.entries(parameters)
         | 
| @@ -28,23 +37,27 @@ export default class S3Engine extends Engine { | |
| 28 37 | 
             
                static async putModel(model) {
         | 
| 29 38 | 
             
                    const Key = [this._configuration.prefix, `${model.id}.json`].join('/');
         | 
| 30 39 |  | 
| 31 | 
            -
                     | 
| 32 | 
            -
                         | 
| 33 | 
            -
             | 
| 34 | 
            -
             | 
| 35 | 
            -
             | 
| 36 | 
            -
             | 
| 40 | 
            +
                    try {
         | 
| 41 | 
            +
                        await this._configuration.client.send(new PutObjectCommand({
         | 
| 42 | 
            +
                            Key,
         | 
| 43 | 
            +
                            Body: JSON.stringify(model.toData()),
         | 
| 44 | 
            +
                            Bucket: this._configuration.bucket,
         | 
| 45 | 
            +
                            ContentType: 'application/json',
         | 
| 46 | 
            +
                        }));
         | 
| 47 | 
            +
                    } catch (error) {
         | 
| 48 | 
            +
                        throw new FailedPutS3EngineError(`Failed to put s3://${this._configuration.bucket}/${Key}`, error);
         | 
| 49 | 
            +
                    }
         | 
| 37 50 | 
             
                }
         | 
| 38 51 |  | 
| 39 52 | 
             
                static async getIndex(location) {
         | 
| 40 53 | 
             
                    try {
         | 
| 41 54 | 
             
                        const data = await this._configuration.client.send(new GetObjectCommand({
         | 
| 42 | 
            -
                            Key: [this._configuration.prefix | 
| 55 | 
            +
                            Key: [this._configuration.prefix, location, '_index.json'].filter(e => !!e).join('/'),
         | 
| 43 56 | 
             
                            Bucket: this._configuration.bucket,
         | 
| 44 57 | 
             
                        }));
         | 
| 45 58 |  | 
| 46 59 | 
             
                        return JSON.parse(await data.Body.transformToString());
         | 
| 47 | 
            -
                    } catch ( | 
| 60 | 
            +
                    } catch (_) {
         | 
| 48 61 | 
             
                        return {};
         | 
| 49 62 | 
             
                    }
         | 
| 50 63 | 
             
                }
         | 
| @@ -52,19 +65,23 @@ export default class S3Engine extends Engine { | |
| 52 65 | 
             
                static async putIndex(index) {
         | 
| 53 66 | 
             
                    const processIndex = async (location, models) => {
         | 
| 54 67 | 
             
                        const modelIndex = Object.fromEntries(models.map(m => [m.id, m.toIndexData()]));
         | 
| 55 | 
            -
                        const Key = [this._configuration.prefix | 
| 68 | 
            +
                        const Key = [this._configuration.prefix, location, '_index.json'].filter(e => !!e).join('/');
         | 
| 56 69 |  | 
| 57 70 | 
             
                        const currentIndex = await this.getIndex(location);
         | 
| 58 71 |  | 
| 59 | 
            -
                         | 
| 60 | 
            -
                             | 
| 61 | 
            -
             | 
| 62 | 
            -
             | 
| 63 | 
            -
             | 
| 64 | 
            -
                                 | 
| 65 | 
            -
             | 
| 66 | 
            -
             | 
| 67 | 
            -
             | 
| 72 | 
            +
                        try {
         | 
| 73 | 
            +
                            await this._configuration.client.send(new PutObjectCommand({
         | 
| 74 | 
            +
                                Key,
         | 
| 75 | 
            +
                                Bucket: this._configuration.bucket,
         | 
| 76 | 
            +
                                ContentType: 'application/json',
         | 
| 77 | 
            +
                                Body: JSON.stringify({
         | 
| 78 | 
            +
                                    ...currentIndex,
         | 
| 79 | 
            +
                                    ...modelIndex,
         | 
| 80 | 
            +
                                }),
         | 
| 81 | 
            +
                            }));
         | 
| 82 | 
            +
                        } catch (error) {
         | 
| 83 | 
            +
                            throw new FailedPutS3EngineError(`Failed to put s3://${this._configuration.bucket}/${Key}`, error);
         | 
| 84 | 
            +
                        }
         | 
| 68 85 | 
             
                    };
         | 
| 69 86 |  | 
| 70 87 | 
             
                    for (const [location, models] of Object.entries(index)) {
         | 
| @@ -75,50 +92,49 @@ export default class S3Engine extends Engine { | |
| 75 92 | 
             
                }
         | 
| 76 93 |  | 
| 77 94 | 
             
                static async getSearchIndexCompiled(model) {
         | 
| 78 | 
            -
                     | 
| 79 | 
            -
                         | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
                         | 
| 83 | 
            -
             | 
| 84 | 
            -
                        return JSON.parse(await data.Body.transformToString());
         | 
| 85 | 
            -
                    } catch (_error) {
         | 
| 86 | 
            -
                        return {};
         | 
| 87 | 
            -
                    }
         | 
| 95 | 
            +
                    return await this._configuration.client.send(new GetObjectCommand({
         | 
| 96 | 
            +
                        Key: [this._configuration.prefix, model.name, '_search_index.json'].join('/'),
         | 
| 97 | 
            +
                        Bucket: this._configuration.bucket,
         | 
| 98 | 
            +
                    })).then(data => data.Body.transformToString())
         | 
| 99 | 
            +
                        .then(JSON.parse);
         | 
| 88 100 | 
             
                }
         | 
| 89 101 |  | 
| 90 102 | 
             
                static async getSearchIndexRaw(model) {
         | 
| 91 | 
            -
                     | 
| 92 | 
            -
                         | 
| 93 | 
            -
             | 
| 94 | 
            -
             | 
| 95 | 
            -
                         | 
| 96 | 
            -
             | 
| 97 | 
            -
                        return JSON.parse(await data.Body.transformToString());
         | 
| 98 | 
            -
                    } catch (_error) {
         | 
| 99 | 
            -
                        return {};
         | 
| 100 | 
            -
                    }
         | 
| 103 | 
            +
                    return await this._configuration.client.send(new GetObjectCommand({
         | 
| 104 | 
            +
                        Key: [this._configuration.prefix, model.name, '_search_index_raw.json'].join('/'),
         | 
| 105 | 
            +
                        Bucket: this._configuration.bucket,
         | 
| 106 | 
            +
                    })).then(data => data.Body.transformToString())
         | 
| 107 | 
            +
                        .then(JSON.parse)
         | 
| 108 | 
            +
                        .catch(() => ({}));
         | 
| 101 109 | 
             
                }
         | 
| 102 110 |  | 
| 103 111 | 
             
                static async putSearchIndexCompiled(model, compiledIndex) {
         | 
| 104 112 | 
             
                    const Key = [this._configuration.prefix, model.name, '_search_index.json'].join('/');
         | 
| 105 113 |  | 
| 106 | 
            -
                     | 
| 107 | 
            -
                         | 
| 108 | 
            -
             | 
| 109 | 
            -
             | 
| 110 | 
            -
             | 
| 111 | 
            -
             | 
| 114 | 
            +
                    try {
         | 
| 115 | 
            +
                        await this._configuration.client.send(new PutObjectCommand({
         | 
| 116 | 
            +
                            Key,
         | 
| 117 | 
            +
                            Body: JSON.stringify(compiledIndex),
         | 
| 118 | 
            +
                            Bucket: this._configuration.bucket,
         | 
| 119 | 
            +
                            ContentType: 'application/json',
         | 
| 120 | 
            +
                        }));
         | 
| 121 | 
            +
                    } catch (error) {
         | 
| 122 | 
            +
                        throw new FailedPutS3EngineError(`Failed to put s3://${this._configuration.bucket}/${Key}`, error);
         | 
| 123 | 
            +
                    }
         | 
| 112 124 | 
             
                }
         | 
| 113 125 |  | 
| 114 126 | 
             
                static async putSearchIndexRaw(model, rawIndex) {
         | 
| 115 127 | 
             
                    const Key = [this._configuration.prefix, model.name, '_search_index_raw.json'].join('/');
         | 
| 116 128 |  | 
| 117 | 
            -
                     | 
| 118 | 
            -
                         | 
| 119 | 
            -
             | 
| 120 | 
            -
             | 
| 121 | 
            -
             | 
| 122 | 
            -
             | 
| 129 | 
            +
                    try {
         | 
| 130 | 
            +
                        await this._configuration.client.send(new PutObjectCommand({
         | 
| 131 | 
            +
                            Key,
         | 
| 132 | 
            +
                            Body: JSON.stringify(rawIndex),
         | 
| 133 | 
            +
                            Bucket: this._configuration.bucket,
         | 
| 134 | 
            +
                            ContentType: 'application/json',
         | 
| 135 | 
            +
                        }));
         | 
| 136 | 
            +
                    } catch (error) {
         | 
| 137 | 
            +
                        throw new FailedPutS3EngineError(`Failed to put s3://${this._configuration.bucket}/${Key}`, error);
         | 
| 138 | 
            +
                    }
         | 
| 123 139 | 
             
                }
         | 
| 124 140 | 
             
            }
         | 
    
        package/src/type/Model.js
    CHANGED
    
    
| @@ -3,7 +3,7 @@ import Type from '../Type.js'; | |
| 3 3 | 
             
            export default class ArrayType {
         | 
| 4 4 | 
             
                static of(type) {
         | 
| 5 5 | 
             
                    class ArrayOf extends Type {
         | 
| 6 | 
            -
                        static _type = 'array'
         | 
| 6 | 
            +
                        static _type = 'array';
         | 
| 7 7 | 
             
                        static _items = type;
         | 
| 8 8 |  | 
| 9 9 | 
             
                        static toString() {
         | 
| @@ -19,7 +19,7 @@ export default class ArrayType { | |
| 19 19 | 
             
                                }
         | 
| 20 20 | 
             
                            }
         | 
| 21 21 |  | 
| 22 | 
            -
                            Object.defineProperty(Required, 'name', {value: `Required${this.toString()}Type`})
         | 
| 22 | 
            +
                            Object.defineProperty(Required, 'name', {value: `Required${this.toString()}Type`});
         | 
| 23 23 |  | 
| 24 24 | 
             
                            return Required;
         | 
| 25 25 | 
             
                        }
         |