@stonyx/orm 0.1.0 → 0.2.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 CHANGED
@@ -1,13 +1,305 @@
1
- # stonyx-orm
1
+ # @stonyx/orm
2
2
 
3
- ## Running the test suite
3
+ A lightweight ORM for Stonyx projects, featuring model definitions, serializers, relationships, transforms, and optional REST server integration.
4
+ `@stonyx/orm` provides a structured way to define models, manage relationships, and persist data in JSON files. It also allows integration with the Stonyx REST server for automatic route setup and access control.
5
+
6
+ ## Highlights
7
+
8
+ - **Automatic Loading**: Models, serializers, transforms, and access classes are auto-registered from their configured directories.
9
+ - **Models**: Define attributes with type-safe proxies (`attr`) and relationships (`hasMany`, `belongsTo`).
10
+ - **Serializers**: Map raw data into model-friendly structures, including nested properties.
11
+ - **Transforms**: Apply custom transformations on data values automatically.
12
+ - **DB Integration**: Optional file-based persistence with auto-save support.
13
+ - **REST Server Integration**: Automatic route setup with customizable access control.
14
+
15
+ ## Installation
16
+
17
+ ```bash
18
+ npm install @stonyx/orm
19
+ ````
20
+
21
+ ## Usage example
22
+
23
+ This module is part of the **Stonyx framework**. To use it, first configure the `restServer` key in your `environment.js` file:
24
+
25
+ ```js
26
+ const {
27
+ ORM_ACCESS_PATH,
28
+ ORM_MODEL_PATH,
29
+ ORM_REST_ROUTE,
30
+ ORM_SERIALIZER_PATH,
31
+ ORM_TRANSFORM_PATH,
32
+ ORM_USE_REST_SERVER,
33
+ DB_AUTO_SAVE,
34
+ DB_FILE,
35
+ DB_SCHEMA_PATH,
36
+ DB_SAVE_INTERVAL
37
+ } = process;
38
+
39
+ export default {
40
+ orm: {
41
+ logColor: 'white',
42
+ logMethod: 'db',
43
+
44
+ db: {
45
+ autosave: DB_AUTO_SAVE ?? 'false',
46
+ file: DB_FILE ?? 'db.json',
47
+ saveInterval: DB_SAVE_INTERVAL ?? 3600, // 1 hour
48
+ schema: DB_SCHEMA_PATH ?? './config/db-schema.js'
49
+ },
50
+ paths: {
51
+ access: ORM_ACCESS_PATH ?? './access',
52
+ model: ORM_MODEL_PATH ?? './models',
53
+ serializer: ORM_SERIALIZER_PATH ?? './serializers',
54
+ transform: ORM_TRANSFORM_PATH ?? './transforms'
55
+ },
56
+ restServer: {
57
+ enabled: ORM_USE_REST_SERVER ?? 'true',
58
+ route: ORM_REST_ROUTE ?? '/'
59
+ }
60
+ }
61
+ };
62
+ ```
63
+
64
+ Then initialize the Stonyx framework, which auto-initializes all of its modules, including `@stonyx/rest-server`:
65
+
66
+ ```js
67
+ import Stonyx from 'stonyx';
68
+ import config from './config/environment.js';
69
+
70
+ new Stonyx(config);
71
+ ```
72
+
73
+ For further framework initialization instructions, see the [Stonyx repository](https://github.com/abofs/stonyx).
74
+
75
+ ## Models
76
+
77
+ Define a model with attributes and relationships:
78
+
79
+ ```js
80
+ import { Model, attr, hasMany, belongsTo } from '@stonyx/orm';
81
+
82
+ export default class OwnerModel extends Model {
83
+ id = attr('string');
84
+ age = attr('number');
85
+ pets = hasMany('animal');
86
+
87
+ get totalPets() {
88
+ return this.pets.length;
89
+ }
90
+ }
91
+ ```
92
+
93
+ ## Serializers
94
+
95
+ Based on the following sample payload structure which represents a poorly structure third-party data source:
96
+
97
+ ```js
98
+ export default {
99
+ animals: [
100
+ { id: 1, type: 'dog', details: { age: 2, c: 'small', x: 'black', location: { type: 'farm', owner: 'angela' }}},
101
+ //...
102
+ ]
103
+ }
104
+ ```
105
+
106
+ Map raw data to model fields:
107
+
108
+ ```js
109
+ import { Serializer } from '@stonyx/orm';
110
+
111
+ export default class AnimalSerializer extends Serializer {
112
+ map = {
113
+ age: 'details.age',
114
+ size: 'details.c',
115
+ color: 'details.x',
116
+ owner: 'details.location.owner'
117
+ }
118
+ }
119
+ ```
120
+
121
+ ## Relationships
122
+
123
+ ### belongsTo
124
+
125
+ ```js
126
+ import { belongsTo } from '@stonyx/orm';
127
+
128
+ class AnimalModel extends Model {
129
+ owner = belongsTo('owner');
130
+ }
131
+ ```
132
+
133
+ ### hasMany
134
+
135
+ ```js
136
+ import { hasMany } from '@stonyx/orm';
137
+
138
+ class OwnerModel extends Model {
139
+ pets = hasMany('animal');
140
+ }
141
+ ```
142
+
143
+ ## Transforms
144
+
145
+ Apply custom transforms on field values:
146
+
147
+ ```js
148
+ import { ANIMALS } from '../constants.js';
149
+
150
+ export default function(value) {
151
+ return ANIMALS.indexOf(value) || 0;
152
+ }
153
+ ```
154
+
155
+ ## Database (DB) Integration
156
+
157
+ The ORM can automatically save records to a JSON file with optional auto-save intervals.
158
+
159
+ ```js
160
+ import Orm from '@stonyx/orm';
161
+
162
+ const orm = new Orm();
163
+ await orm.init();
164
+
165
+ // Access the DB record
166
+ const dbRecord = Orm.db;
167
+ ```
168
+
169
+ Configuration options are in `config/environment.js`:
170
+
171
+ * `DB_AUTO_SAVE`: Whether to auto-save.
172
+ * `DB_FILE`: File path to store data.
173
+ * `DB_SAVE_INTERVAL`: Interval in seconds for auto-save.
174
+ * `DB_SCHEMA_PATH`: Path to DB schema.
175
+
176
+ ## REST Server Integration
177
+
178
+ The ORM can automatically register REST routes using your access classes.
179
+
180
+ ```js
181
+ import setupRestServer from '@stonyx/orm/setup-rest-server';
182
+
183
+ await setupRestServer('/', './access');
184
+ ```
185
+
186
+ Access classes define models and provide custom filtering/authorization logic:
187
+
188
+ ```js
189
+ export default class GlobalAccess {
190
+ models = ['owner', 'animal'];
191
+
192
+ access(request) {
193
+ if (request.url.endsWith('/owner/angela')) return false;
194
+ return ['read', 'create', 'update', 'delete'];
195
+ }
196
+ }
197
+ ```
198
+
199
+ ### Include Parameter (Sideloading Relationships)
200
+
201
+ The ORM supports JSON API-compliant relationship sideloading via the `include` query parameter. This reduces the need for multiple API requests by embedding related records in a single response.
202
+
203
+ #### Basic Usage
204
+
205
+ ```javascript
206
+ // Fetch animal with owner and traits included
207
+ GET /animals/1?include=owner,traits
208
+
209
+ // Response:
210
+ {
211
+ "data": {
212
+ "type": "animal",
213
+ "id": 1,
214
+ "attributes": { "age": 2, "size": "small" },
215
+ "relationships": {
216
+ "owner": { "data": { "type": "owner", "id": "angela" } },
217
+ "traits": { "data": [
218
+ { "type": "trait", "id": 1 },
219
+ { "type": "trait", "id": 2 }
220
+ ]}
221
+ }
222
+ },
223
+ "included": [
224
+ {
225
+ "type": "owner",
226
+ "id": "angela",
227
+ "attributes": { "age": 36, "gender": "female" },
228
+ "relationships": { "pets": { "data": [...] } }
229
+ },
230
+ {
231
+ "type": "trait",
232
+ "id": 1,
233
+ "attributes": { "type": "habitat", "value": "farm" },
234
+ "relationships": {}
235
+ },
236
+ {
237
+ "type": "trait",
238
+ "id": 2,
239
+ "attributes": { "type": "color", "value": "black" },
240
+ "relationships": {}
241
+ }
242
+ ]
243
+ }
4
244
  ```
5
- npm test
245
+
246
+ #### Features
247
+
248
+ - **Comma-separated relationship names:** `?include=owner,traits`
249
+ - **Nested relationship traversal:** `?include=owner.pets,owner.company` (supports multi-level nesting)
250
+ - **Works with collections and single records:** Both GET endpoints support includes
251
+ - **Automatic deduplication:** Each unique record (by type+id) appears only once in included array
252
+ - **Invalid relationships ignored:** Invalid relationship names are silently skipped
253
+ - **Backward compatible:** Omit the include parameter for original behavior (no included array)
254
+
255
+ #### Examples
256
+
257
+ ```javascript
258
+ // Single resource with single include
259
+ GET /owners/gina?include=pets
260
+
261
+ // Single resource with multiple includes
262
+ GET /animals/1?include=owner,traits
263
+
264
+ // Nested includes (NEW!)
265
+ GET /animals/1?include=owner.pets
266
+
267
+ // Deep nesting (3+ levels)
268
+ GET /scenes/e001-s001?include=slides.dialogue.character
269
+
270
+ // Collection with includes (deduplicates automatically)
271
+ GET /animals?include=owner
272
+
273
+ // Combining nested and non-nested includes
274
+ GET /owners?include=pets.traits,company
275
+
276
+ // No include parameter (backward compatible)
277
+ GET /animals/1
278
+ // Returns: { data: {...} } // No included array
6
279
  ```
7
280
 
8
- ## TODO:
9
- - Config validation
10
- - Use file utils for serializer/model loading
11
- - Usage Documentation
12
- - TEST: Core functionality
13
- - TEST: Base transforms
281
+ **How Nested Includes Work:**
282
+ 1. Query param parsed into path segments: `owner.pets` → `['owner', 'pets']`
283
+ 2. Recursively traverses relationships depth-first
284
+ 3. Deduplication still by type+id (no duplicates in included array)
285
+ 4. Gracefully handles null/missing relationships at any depth
286
+ 5. Each included record gets full `toJSON()` representation
287
+
288
+ #### Limitations
289
+
290
+ - Only available on GET endpoints (not POST/PATCH)
291
+
292
+ ## Exported Helpers
293
+
294
+ | Export | Description |
295
+ | --------------- | ----------------------------------------------------------------------- |
296
+ | `attr` | Define model attributes with type-safe proxy. |
297
+ | `belongsTo` | Define a one-to-one relationship. |
298
+ | `hasMany` | Define a one-to-many relationship. |
299
+ | `createRecord` | Instantiate a record with proper serialization and relationships. |
300
+ | `store` | Singleton store for all model instances. |
301
+ | `relationships` | Access all relationships (`hasMany`, `belongsTo`, `global`, `pending`). |
302
+
303
+ ## License
304
+
305
+ Apache — do what you want, just keep attribution.
@@ -1,7 +1,10 @@
1
1
  const {
2
+ ORM_ACCESS_PATH,
2
3
  ORM_MODEL_PATH,
4
+ ORM_REST_ROUTE,
3
5
  ORM_SERIALIZER_PATH,
4
6
  ORM_TRANSFORM_PATH,
7
+ ORM_USE_REST_SERVER,
5
8
  DB_AUTO_SAVE,
6
9
  DB_FILE,
7
10
  DB_SCHEMA_PATH,
@@ -19,8 +22,13 @@ export default {
19
22
  schema: DB_SCHEMA_PATH ?? './config/db-schema.js'
20
23
  },
21
24
  paths: {
25
+ access: ORM_ACCESS_PATH ?? './access', // Optional for restServer access hooks
22
26
  model: ORM_MODEL_PATH ?? './models',
23
27
  serializer: ORM_SERIALIZER_PATH ?? './serializers',
24
28
  transform: ORM_TRANSFORM_PATH ?? './transforms'
29
+ },
30
+ restServer: {
31
+ enabled: ORM_USE_REST_SERVER ?? 'true', // Whether to load restServer for automatic route setup or
32
+ route: ORM_REST_ROUTE ?? '/',
25
33
  }
26
34
  }
package/package.json CHANGED
@@ -4,17 +4,16 @@
4
4
  "stonyx-async",
5
5
  "stonyx-module"
6
6
  ],
7
- "version": "0.1.0",
7
+ "version": "0.2.0",
8
8
  "description": "",
9
9
  "main": "src/main.js",
10
10
  "type": "module",
11
11
  "exports": {
12
- ".": "./src/main.js",
13
- "./db": "./src/db.js",
14
- "./utils": "./src/utils.js"
12
+ ".": "./src/index.js",
13
+ "./db": "./src/exports/db.js"
15
14
  },
16
15
  "scripts": {
17
- "test": "qunit"
16
+ "test": "qunit --require ./stonyx-bootstrap.cjs"
18
17
  },
19
18
  "repository": {
20
19
  "type": "git",
@@ -25,16 +24,21 @@
25
24
  "contributors": [
26
25
  "Stone Costa <stone.costa@synamicd.com>"
27
26
  ],
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
28
30
  "bugs": {
29
31
  "url": "https://github.com/abofs/stonyx-orm/issues"
30
32
  },
31
33
  "homepage": "https://github.com/abofs/stonyx-orm#readme",
32
34
  "dependencies": {
33
- "stonyx": "^0.1.0"
35
+ "stonyx": "^0.2.2"
34
36
  },
35
37
  "devDependencies": {
36
- "@stonyx/cron": "^0.1.0",
37
- "@stonyx/utils": "^0.1.0",
38
- "qunit": "^2.24.1"
38
+ "@stonyx/cron": "^0.2.0",
39
+ "@stonyx/rest-server": "^0.2.0",
40
+ "@stonyx/utils": "^0.2.2",
41
+ "qunit": "^2.24.1",
42
+ "sinon": "^21.0.0"
39
43
  }
40
44
  }
package/src/attr.js ADDED
@@ -0,0 +1,28 @@
1
+ import ModelProperty from './model-property.js';
2
+
3
+ export default function attr() {
4
+ const modelProp = new ModelProperty(...arguments);
5
+
6
+ return new Proxy(modelProp, {
7
+ get(target, prop, receiver) {
8
+ if (prop === 'valueOf' || prop === 'toString') {
9
+ return () => target.value;
10
+ }
11
+
12
+ if (prop in target) {
13
+ return Reflect.get(target, prop, receiver);
14
+ }
15
+
16
+ return target.value;
17
+ },
18
+
19
+ set(target, prop, value, receiver) {
20
+ if (prop === 'value') {
21
+ target.value = value;
22
+ return true;
23
+ }
24
+
25
+ return Reflect.set(target, prop, value, receiver);
26
+ }
27
+ });
28
+ }
@@ -0,0 +1,63 @@
1
+ import { createRecord, relationships, store } from '@stonyx/orm';
2
+ import { getRelationships } from './relationships.js';
3
+
4
+ function getOrSet(map, key, defaultValue) {
5
+ if (!map.has(key)) map.set(key, defaultValue);
6
+ return map.get(key);
7
+ }
8
+
9
+ export default function belongsTo(modelName) {
10
+ const hasManyRelationships = relationships.get('hasMany');
11
+ const pendingHasManyQueue = relationships.get('pending');
12
+ const pendingBelongsToQueue = relationships.get('pendingBelongsTo');
13
+
14
+ return (sourceRecord, rawData, options) => {
15
+ if (!rawData) return null;
16
+
17
+ const { __name: sourceModelName } = sourceRecord.__model;
18
+ const relationshipId = sourceRecord.id;
19
+ const relationshipKey = options._relationshipKey;
20
+ const relationship = getRelationships('belongsTo', sourceModelName, modelName, relationshipId);
21
+ const modelStore = store.get(modelName);
22
+
23
+ // Try to get existing record
24
+ const output = typeof rawData === 'object'
25
+ ? createRecord(modelName, rawData, options)
26
+ : modelStore.get(rawData);
27
+
28
+ // If not found and is a string ID, register as pending
29
+ if (!output && typeof rawData !== 'object') {
30
+ const targetId = rawData;
31
+
32
+ // Register pending belongsTo
33
+ const modelPendingMap = getOrSet(pendingBelongsToQueue, modelName, new Map());
34
+ const targetPendingArray = getOrSet(modelPendingMap, targetId, []);
35
+
36
+ targetPendingArray.push({
37
+ sourceRecord,
38
+ sourceModelName,
39
+ relationshipKey,
40
+ relationshipId
41
+ });
42
+
43
+ relationship.set(relationshipId, null);
44
+ return null;
45
+ }
46
+
47
+ relationship.set(relationshipId, output || {});
48
+
49
+ // Populate hasMany side if the relationship is defined
50
+ const otherSide = hasManyRelationships.get(modelName)?.get(sourceModelName)?.get(output?.id);
51
+
52
+ if (otherSide) {
53
+ otherSide.push(sourceRecord);
54
+
55
+ // Remove pending queue if it was just fulfilled
56
+ const pendingModelRelationships = pendingHasManyQueue.get(sourceModelName);
57
+
58
+ if (pendingModelRelationships) pendingModelRelationships.delete(relationshipId);
59
+ }
60
+
61
+ return output;
62
+ }
63
+ }
package/src/db.js CHANGED
@@ -1,18 +1,26 @@
1
- /**
2
- * TODO:
3
- *
4
- * ORM DB usage assumes that 100% of the data is ORM driven
5
- * With that assumption, we can safely do the following
6
- * - On save: Remove computed properties (getters) from data set
7
- * - On load: Compute computed properties from data set
8
- * - Error handling: Warn of non-ORM properties found in data (but load them)
9
- * - Optional configuration flag to disable these warnings
1
+ /*
2
+ * Copyright 2025 Stone Costa
3
+ *
4
+ * Licensed under the Apache License, Version 2.0 (the "License");
5
+ * you may not use this file except in compliance with the License.
6
+ * You may obtain a copy of the License at
7
+ *
8
+ * http://www.apache.org/licenses/LICENSE-2.0
9
+ *
10
+ * Unless required by applicable law or agreed to in writing, software
11
+ * distributed under the License is distributed on an "AS IS" BASIS,
12
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ * See the License for the specific language governing permissions and
14
+ * limitations under the License.
10
15
  */
16
+
11
17
  import Cron from '@stonyx/cron';
12
18
  import config from 'stonyx/config';
13
19
  import log from 'stonyx/log';
20
+ import Orm, { createRecord, store } from '@stonyx/orm';
14
21
  import { createFile, updateFile, readFile } from '@stonyx/utils/file';
15
- import { deepCopy } from '@stonyx/utils/object';
22
+
23
+ export const dbKey = '__db';
16
24
 
17
25
  export default class DB {
18
26
  constructor() {
@@ -21,10 +29,22 @@ export default class DB {
21
29
  DB.instance = this;
22
30
  }
23
31
 
24
- async init() {
25
- await this.retrieve();
32
+ async getSchema() {
33
+ const { rootPath } = config;
34
+ const { file, schema } = config.orm.db;
35
+
36
+ if (!file) throw new Error('Configuration Error: ORM DB file path must be defined.');
26
37
 
38
+ return (await import(`${rootPath}/${schema}`)).default;
39
+ }
40
+
41
+ async init() {
27
42
  const { autosave, saveInterval } = config.orm.db;
43
+
44
+ store.set(dbKey, new Map());
45
+ Orm.instance.models[`${dbKey}Model`] = await this.getSchema();
46
+
47
+ this.record = await this.getRecord();
28
48
 
29
49
  if (autosave !== 'true') return;
30
50
 
@@ -33,74 +53,28 @@ export default class DB {
33
53
 
34
54
  async create() {
35
55
  const { rootPath } = config;
36
- const { file, schema } = config.orm.db;
37
-
38
- if (!file) throw new Error('Configuration Error: ORM DB file path must be defined.');
39
-
40
- let dbSchema;
41
-
42
- try {
43
- dbSchema = (await import(`${rootPath}/${schema}`)).default;
44
- } catch (error) {
45
- dbSchema = {};
46
- log.db('Unable to load DB schema from file, using empty schema instead');
47
- }
56
+ const { file } = config.orm.db;
48
57
 
49
- const data = deepCopy(dbSchema);
50
-
51
- createFile(`${rootPath}/${file}`, data, { json: true });
58
+ createFile(`${rootPath}/${file}`, {}, { json: true });
52
59
 
53
- return data;
60
+ return {};
54
61
  }
55
62
 
56
63
  async save() {
57
64
  const { file } = config.orm.db;
65
+ const jsonData = this.record.format();
66
+ delete jsonData.id; // Don't save id
58
67
 
59
- await updateFile(`${config.rootPath}/${file}`, this.data, { json: true });
68
+ await updateFile(`${config.rootPath}/${file}`, jsonData, { json: true });
60
69
 
61
70
  log.db(`DB has been successfully saved to ${file}`);
62
71
  }
63
72
 
64
- async retrieve() {
73
+ async getRecord() {
65
74
  const { file } = config.orm.db;
66
75
 
67
- this.data = await readFile(file, { json: true, missingFileCallback: this.create.bind(this) });
68
- }
69
-
70
- /** TODO: We need ORM specific reload logic that replaces models attributes when loading from DB */
71
- // _tempORMSerializeMeta(data) {
72
- // const { meta } = data;
73
-
74
- // // HACK: Create map to ensure we have no duplicate references
75
- // // This will no longer be necessary once once gatherer method prevents duplicates
76
- // const referenceIds = {};
77
- // const { shipmentReportReferences } = meta;
76
+ const data = await readFile(file, { json: true, missingFileCallback: this.create.bind(this) });
78
77
 
79
- // // Fix reference dates & remove duplicates
80
- // for (let i = shipmentReportReferences.length - 1; i >= 0; i--) {
81
- // const record = shipmentReportReferences[i];
82
-
83
- // if (!referenceIds[record.id]) {
84
- // referenceIds[record.id] = record;
85
- // } else {
86
- // shipmentReportReferences.splice(i, 1);
87
- // }
88
-
89
- // if (!record.date) continue;
90
-
91
- // record.date = new Date(record.date);
92
- // }
93
-
94
- // // Re-compute
95
- // const metaModel = new MODELS.MetaModel();
96
-
97
- // // Serialize computed properties
98
- // for (const [key, method] of getComputedProperties(metaModel)) {
99
- // const value = method.bind(meta)();
100
-
101
- // meta[key] = value;
102
- // }
103
-
104
- // return data;
105
- // }
78
+ return createRecord(dbKey, data, { isDbRecord: true, serialize: false, transform: false });
79
+ }
106
80
  }
@@ -0,0 +1,7 @@
1
+ import Orm from '@stonyx/orm';
2
+
3
+ const { db } = Orm;
4
+
5
+ export default db;
6
+ export const data = db.record;
7
+ export const saveDB = db.save.bind(db);
@@ -0,0 +1,61 @@
1
+ import { createRecord, relationships, store } from '@stonyx/orm';
2
+ import { getRelationships } from './relationships.js';
3
+ import { getOrSet, makeArray } from '@stonyx/utils/object';
4
+ import { dbKey } from './db.js';
5
+
6
+ function queuePendingRelationship(pendingRelationshipQueue, pendingRelationships, modelName, id) {
7
+ pendingRelationshipQueue.push({
8
+ pendingRelationship: getOrSet(pendingRelationships, modelName, new Map()),
9
+ id
10
+ });
11
+
12
+ return null;
13
+ }
14
+
15
+ export default function hasMany(modelName) {
16
+ const globalRelationships = relationships.get('global');
17
+ const pendingRelationships = relationships.get('pending');
18
+
19
+ return (sourceRecord, rawData, options) => {
20
+ const { __name: sourceModelName } = sourceRecord.__model;
21
+ const relationshipId = sourceRecord.id;
22
+ const relationship = getRelationships('hasMany', sourceModelName, modelName, relationshipId);
23
+ const modelStore = store.get(modelName);
24
+ const pendingRelationshipQueue = [];
25
+
26
+ const output = !rawData ? [] : makeArray(rawData).map(elementData => {
27
+ let record;
28
+
29
+ if (typeof elementData !== 'object') {
30
+ record = modelStore.get(elementData);
31
+
32
+ if (!record) {
33
+ return queuePendingRelationship(pendingRelationshipQueue, pendingRelationships, modelName, elementData);
34
+ }
35
+ } else {
36
+ if (elementData !== Object(elementData)) {
37
+ return queuePendingRelationship(pendingRelationshipQueue, pendingRelationships, modelName, elementData);
38
+ }
39
+
40
+ record = createRecord(modelName, elementData, options);
41
+ }
42
+
43
+ // Populate belongTo side if the relationship is defined
44
+ const otherSide = relationships.get('belongsTo').get(modelName)?.get(sourceModelName)?.get(record.id);
45
+
46
+ if (otherSide) Object.assign(otherSide, sourceRecord);
47
+
48
+ return record;
49
+ }).filter(value => value);
50
+
51
+ relationship.set(relationshipId, output);
52
+
53
+ // Assign global relationship
54
+ if (options.global || sourceModelName === dbKey) getOrSet(globalRelationships, modelName, []).push(output);
55
+
56
+ // Assign pending relationships
57
+ for (const { pendingRelationship, id } of pendingRelationshipQueue) getOrSet(pendingRelationship, id, []).push(output);
58
+
59
+ return output;
60
+ }
61
+ }