Skip to content

Commit 65ce409

Browse files
authored
feat: support mongodb cloud customizations (#1097)
1 parent a6c3260 commit 65ce409

File tree

16 files changed

+701
-302
lines changed

16 files changed

+701
-302
lines changed

.github/workflows/build.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ jobs:
3232
path: |
3333
node_modules
3434
packages/*/node_modules
35-
key: ${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
35+
key: ${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}-${{ hashFiles('packages/*/package.json') }}
3636
- name: Install & Bootstrap
3737
run: yarn && yarn bootstrap --ci
3838
- name: Build
@@ -89,7 +89,7 @@ jobs:
8989
path: |
9090
node_modules
9191
packages/*/node_modules
92-
key: ${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
92+
key: ${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}-${{ hashFiles('packages/*/package.json') }}
9393
fail-on-cache-miss: true
9494
- name: Restore build from cache
9595
uses: actions/cache/restore@v3
@@ -142,7 +142,7 @@ jobs:
142142
path: |
143143
node_modules
144144
packages/*/node_modules
145-
key: ${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
145+
key: ${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}-${{ hashFiles('packages/*/package.json') }}
146146
fail-on-cache-miss: true
147147
- name: Restore build from cache
148148
uses: actions/cache/restore@v3
@@ -184,7 +184,7 @@ jobs:
184184
path: |
185185
node_modules
186186
packages/*/node_modules
187-
key: ${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}
187+
key: ${{ runner.os }}-modules-${{ hashFiles('yarn.lock') }}-${{ hashFiles('packages/*/package.json') }}
188188
fail-on-cache-miss: true
189189
- name: Restore build from cache
190190
uses: actions/cache/restore@v3

packages/datasource-mongo/src/index.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import type { Introspection } from './introspection/types';
22
import type { ConnectionParams, IntrospectorParams, MongoDatasourceParams } from './types';
33
import type { DataSourceFactory, Logger } from '@forestadmin/datasource-toolkit';
4-
import type { Connection } from 'mongoose';
54

65
import { MongooseDatasource } from '@forestadmin/datasource-mongoose';
7-
import mongoose from 'mongoose';
6+
import mongoose, { Connection, Mongoose } from 'mongoose';
87

98
import Introspector from './introspection/introspector';
109
import listCollectionsFromIntrospection from './introspection/list-collections-from-introspection';
@@ -56,6 +55,13 @@ export async function buildMongooseInstance(
5655
return connection;
5756
}
5857

58+
export function buildDisconnectedMongooseInstance(introspection: Introspection): Mongoose {
59+
const mongooseInstance = new Mongoose();
60+
OdmBuilder.defineModels(mongooseInstance, introspection.models);
61+
62+
return mongooseInstance;
63+
}
64+
5965
export function createMongoDataSource(
6066
params: MongoDatasourceParams,
6167
options?: { introspection: Introspection },

packages/datasource-mongo/src/odm-builder/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/* eslint-disable @typescript-eslint/no-non-null-assertion */
2-
import { Connection, Schema } from 'mongoose';
2+
import { Connection, Mongoose, Schema } from 'mongoose';
33

44
import { ModelAnalysis, ModelDefinition, PrimitiveDefinition } from '../introspection/types';
55

@@ -14,7 +14,7 @@ export default class OdmBuilder {
1414
ObjectId: Schema.Types.ObjectId,
1515
};
1616

17-
static defineModels(connection: Connection, study: ModelDefinition[]) {
17+
static defineModels(connection: Connection | Mongoose, study: ModelDefinition[]) {
1818
for (const collection of study) {
1919
const definition = this.buildDefinition(collection.analysis);
2020
connection.model(collection.name, new Schema(definition), collection.name);
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { Introspection, buildDisconnectedMongooseInstance } from '../src';
2+
3+
describe('Datasource Mongo', () => {
4+
describe('buildDisconnectedMongooseInstance', () => {
5+
it('should build a mongoose instance with the given introspection', () => {
6+
const introspection: Introspection = {
7+
source: '@forestadmin/datasource-mongo',
8+
version: 3,
9+
models: [
10+
{
11+
name: 'comments',
12+
analysis: {
13+
type: 'object',
14+
object: {
15+
_id: { type: 'ObjectId', nullable: false },
16+
date: { type: 'Date', nullable: false },
17+
name: { type: 'string', nullable: false },
18+
text: { type: 'string', nullable: false },
19+
email: { type: 'string', nullable: false },
20+
movie_id: { type: 'ObjectId', nullable: false },
21+
},
22+
nullable: false,
23+
},
24+
},
25+
{
26+
name: 'movies',
27+
analysis: {
28+
type: 'object',
29+
object: {
30+
_id: { type: 'ObjectId', nullable: false },
31+
title: { type: 'string', nullable: false },
32+
tomatoes: {
33+
type: 'object',
34+
object: {
35+
critic: {
36+
type: 'object',
37+
object: {
38+
meter: { type: 'number', nullable: false },
39+
rating: { type: 'number', nullable: false },
40+
numReviews: { type: 'number', nullable: false },
41+
},
42+
nullable: false,
43+
},
44+
},
45+
nullable: false,
46+
},
47+
lastupdated: { type: 'string', nullable: false },
48+
},
49+
nullable: false,
50+
},
51+
},
52+
],
53+
};
54+
55+
const mongoose = buildDisconnectedMongooseInstance(introspection);
56+
57+
expect(mongoose).toBeDefined();
58+
59+
expect(mongoose.models.comments).toBeDefined();
60+
expect(mongoose.models.movies).toBeDefined();
61+
});
62+
});
63+
});

packages/datasource-mongoose/src/datasource.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { BaseDataSource, Logger } from '@forestadmin/datasource-toolkit';
2-
import { Connection, Model } from 'mongoose';
2+
import { Connection, Model, Mongoose } from 'mongoose';
33

44
import MongooseCollection from './collection';
55
import MongooseSchema from './mongoose/schema';
@@ -9,7 +9,11 @@ import OptionsParser from './utils/options';
99
import RelationGenerator from './utils/schema/relations';
1010

1111
export default class MongooseDatasource extends BaseDataSource<MongooseCollection> {
12-
constructor(connection: Connection, options: MongooseOptions = {}, logger: Logger = null) {
12+
constructor(
13+
connection: Connection | Mongoose,
14+
options: MongooseOptions = {},
15+
logger: Logger = null,
16+
) {
1317
super();
1418

1519
if (options && !options.flattenMode) {

packages/datasource-mongoose/src/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { DataSourceFactory } from '@forestadmin/datasource-toolkit';
2-
import { Connection } from 'mongoose';
2+
import { Connection, Mongoose } from 'mongoose';
33

44
import MongooseDatasource from './datasource';
55
import { MongooseOptions } from './types';
@@ -9,7 +9,7 @@ export { default as MongooseDatasource } from './datasource';
99
export type { MongooseOptions };
1010

1111
export function createMongooseDataSource(
12-
connection: Connection,
12+
connection: Connection | Mongoose,
1313
options: MongooseOptions = {},
1414
): DataSourceFactory {
1515
return async logger => new MongooseDatasource(connection, options, logger);

packages/datasource-sql/src/index.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { Sequelize } from 'sequelize';
66

77
import connect from './connection';
88
import ConnectionOptions from './connection/connection-options';
9+
import SequelizeFactory from './connection/sequelize-factory';
910
import SqlDatasource from './decorators/sql-datasource';
1011
import Introspector from './introspection/introspector';
1112
import listCollectionsFromIntrospection from './introspection/list-collections-from-introspection';
@@ -55,6 +56,17 @@ async function buildModelsAndRelations(
5556
}
5657
}
5758

59+
export async function buildDisconnectedSequelizeInstance(
60+
introspection: SupportedIntrospection,
61+
logger: Logger,
62+
): Promise<Sequelize> {
63+
const options = new ConnectionOptions({ dialect: 'sqlite', sslMode: 'disabled' }, logger);
64+
const sequelize = SequelizeFactory.build(await options.buildSequelizeCtorOptions());
65+
await buildModelsAndRelations(sequelize, logger, introspection);
66+
67+
return sequelize;
68+
}
69+
5870
export async function buildSequelizeInstance(
5971
uriOrOptions: PlainConnectionOptionsOrUri,
6072
logger: Logger,
@@ -91,5 +103,11 @@ export async function preprocessOptions(
91103
}
92104

93105
export * from './connection/errors';
94-
export type { PlainConnectionOptionsOrUri as ConnectionOptions, Table, SslMode, Introspection };
106+
export type {
107+
PlainConnectionOptionsOrUri as ConnectionOptions,
108+
Table,
109+
SslMode,
110+
Introspection,
111+
SupportedIntrospection,
112+
};
95113
export { listCollectionsFromIntrospection };
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import { Sequelize } from 'sequelize';
2+
3+
import { SupportedIntrospection, buildDisconnectedSequelizeInstance } from '../../src';
4+
5+
describe('buildDisconnectedSequelizeInstance', () => {
6+
const introspection: SupportedIntrospection = {
7+
source: '@forestadmin/datasource-sql',
8+
version: 3,
9+
tables: [{ name: 'books', columns: [], schema: 'public', unique: [] }],
10+
views: [],
11+
};
12+
13+
it('should return a Sequelize instance without a running database', async () => {
14+
const sequelize = await buildDisconnectedSequelizeInstance(introspection, jest.fn());
15+
expect(sequelize).toBeInstanceOf(Sequelize);
16+
expect(sequelize.models.books).toBeDefined();
17+
});
18+
});

packages/forest-cloud/package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
"@forestadmin/agent": "1.38.10",
77
"@forestadmin/datasource-customizer": "1.44.1",
88
"@forestadmin/datasource-sql": "1.12.3",
9+
"@forestadmin/datasource-mongo": "1.1.1",
10+
"@forestadmin/datasource-mongoose": "^1.6.7",
11+
"@forestadmin/datasource-sequelize": "^1.8.0",
912
"adm-zip": "^0.5.10",
1013
"apollo-cache-inmemory": "^1.6.6",
1114
"apollo-client": "^2.6.10",
@@ -19,7 +22,6 @@
1922
"graphql-tag": "^2.12.6",
2023
"joi": "^17.12.2",
2124
"ora": "^3.2.0",
22-
"sqlite3": "^5.1.7",
2325
"subscriptions-transport-ws": "^0.9.19",
2426
"ws": "^8.16.0"
2527
},
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import type { Agent } from '../types';
2+
3+
import { CustomizationError } from '../errors';
4+
5+
export default function loadCustomization(agent: Agent, builtCodePath: string): void {
6+
// eslint-disable-next-line
7+
const customization = require(builtCodePath);
8+
const entryPoint = customization?.default || customization;
9+
10+
if (typeof entryPoint !== 'function') {
11+
throw new CustomizationError('Customization file must export a function');
12+
}
13+
14+
try {
15+
entryPoint(agent);
16+
} catch (error) {
17+
throw new CustomizationError(
18+
`Issue with customizations: ${error.name}\n${error.message}`,
19+
error.stack,
20+
);
21+
}
22+
}

0 commit comments

Comments
 (0)