Merge pull request #3341 from specklesystems/fabians/core-ioc-94
chore(server): core IoC #94 - getObjectChildrenFactory
This commit is contained in:
Коммит
73decee881
|
@ -6,7 +6,7 @@ import {
|
|||
SpeckleObjectClosureEntry
|
||||
} from '@/modules/core/domain/objects/types'
|
||||
import { BatchedSelectOptions } from '@/modules/shared/helpers/dbHelper'
|
||||
import { Nullable, Optional } from '@speckle/shared'
|
||||
import { MaybeNullOrUndefined, Nullable, Optional } from '@speckle/shared'
|
||||
import { Knex } from 'knex'
|
||||
import type stream from 'node:stream'
|
||||
|
||||
|
@ -54,6 +54,18 @@ export type GetObjectChildrenStream = (params: {
|
|||
objectId: string
|
||||
}) => Promise<stream.PassThrough & AsyncIterable<{ dataText: string; id: string }>>
|
||||
|
||||
export type GetObjectChildren = (params: {
|
||||
streamId: string
|
||||
objectId: string
|
||||
limit?: MaybeNullOrUndefined<number | string>
|
||||
depth?: MaybeNullOrUndefined<number | string>
|
||||
select?: MaybeNullOrUndefined<string[]>
|
||||
cursor?: MaybeNullOrUndefined<string>
|
||||
}) => Promise<{
|
||||
objects: Omit<SpeckleObject, 'totalChildrenCountByDepth' | 'streamId'>[]
|
||||
cursor: string | null
|
||||
}>
|
||||
|
||||
export type CreateObject = (params: {
|
||||
streamId: string
|
||||
object: RawSpeckleObject
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import { authorizeResolver } from '@/modules/shared'
|
||||
|
||||
import {
|
||||
getObjectChildren,
|
||||
getObjectChildrenQuery
|
||||
} from '@/modules/core/services/objects'
|
||||
import { getObjectChildrenQuery } from '@/modules/core/services/objects'
|
||||
|
||||
import { isNonNullable, Roles } from '@speckle/shared'
|
||||
import { Resolvers } from '@/modules/core/graph/generated/graphql'
|
||||
import {
|
||||
getObjectChildrenFactory,
|
||||
getObjectFactory,
|
||||
storeClosuresIfNotFoundFactory,
|
||||
storeObjectsIfNotFoundFactory
|
||||
|
@ -20,6 +18,7 @@ const createObjects = createObjectsFactory({
|
|||
storeObjectsIfNotFoundFactory: storeObjectsIfNotFoundFactory({ db }),
|
||||
storeClosuresIfNotFound: storeClosuresIfNotFoundFactory({ db })
|
||||
})
|
||||
const getObjectChildren = getObjectChildrenFactory({ db })
|
||||
|
||||
const getStreamObject: NonNullable<Resolvers['Stream']>['object'] =
|
||||
async function object(parent, args) {
|
||||
|
@ -42,14 +41,22 @@ export = {
|
|||
objectId: parent.id,
|
||||
limit: args.limit,
|
||||
depth: args.depth,
|
||||
select: args.select,
|
||||
select: args.select?.filter(isNonNullable),
|
||||
cursor: args.cursor
|
||||
})
|
||||
result.objects.forEach((x) => (x.streamId = parent.streamId))
|
||||
|
||||
// Hacky typing here, but I want to avoid filling up memory with a new array of new objects w/ .map()
|
||||
const objects = result.objects as Array<
|
||||
(typeof result)['objects'][number] & {
|
||||
streamId: string
|
||||
}
|
||||
>
|
||||
objects.forEach((x) => (x.streamId = parent.streamId))
|
||||
|
||||
return {
|
||||
totalCount: parent.totalChildrenCount || 0,
|
||||
cursor: result.cursor,
|
||||
objects: result.objects
|
||||
objects
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -62,7 +62,10 @@ export type ModelsTreeItemGraphQLReturn = Omit<ModelsTreeItem, 'model' | 'childr
|
|||
projectId: string
|
||||
}
|
||||
|
||||
export type ObjectGraphQLReturn = ObjectRecord
|
||||
export type ObjectGraphQLReturn = Omit<
|
||||
ObjectRecord,
|
||||
'createdAt' | 'totalChildrenCountByDepth'
|
||||
>
|
||||
|
||||
/**
|
||||
* Return type for top-level mutations groupings like `projectMutations`, `activeUserMutations` etc.
|
||||
|
|
|
@ -10,6 +10,7 @@ import {
|
|||
GetBatchedStreamObjects,
|
||||
GetFormattedObject,
|
||||
GetObject,
|
||||
GetObjectChildren,
|
||||
GetObjectChildrenStream,
|
||||
GetStreamObjects,
|
||||
StoreClosuresIfNotFound,
|
||||
|
@ -19,6 +20,7 @@ import {
|
|||
} from '@/modules/core/domain/objects/operations'
|
||||
import { SpeckleObject } from '@/modules/core/domain/objects/types'
|
||||
import { SetOptional } from 'type-fest'
|
||||
import { set, toNumber } from 'lodash'
|
||||
|
||||
const ObjectChildrenClosure = buildTableHelper('object_children_closure', [
|
||||
'parent',
|
||||
|
@ -163,3 +165,87 @@ export const getObjectChildrenStreamFactory =
|
|||
.orderBy('objects.id')
|
||||
return q.stream({ highWaterMark: 500 })
|
||||
}
|
||||
|
||||
export const getObjectChildrenFactory =
|
||||
(deps: { db: Knex }): GetObjectChildren =>
|
||||
async ({ streamId, objectId, limit, depth, select, cursor }) => {
|
||||
limit = toNumber(limit || 0) || 50
|
||||
depth = toNumber(depth || 0) || 1000
|
||||
|
||||
let fullObjectSelect = false
|
||||
|
||||
const q = deps.db.with(
|
||||
'object_children_closure',
|
||||
knex.raw(
|
||||
`SELECT objects.id as parent, d.key as child, d.value as mindepth, ? as "streamId"
|
||||
FROM objects
|
||||
JOIN jsonb_each_text(objects.data->'__closure') d ON true
|
||||
where objects.id = ?`,
|
||||
[streamId, objectId]
|
||||
)
|
||||
)
|
||||
|
||||
if (Array.isArray(select)) {
|
||||
select.forEach((field, index) => {
|
||||
q.select(
|
||||
knex.raw('jsonb_path_query(data, :path) as :name:', {
|
||||
path: '$.' + field,
|
||||
name: '' + index
|
||||
})
|
||||
)
|
||||
})
|
||||
} else {
|
||||
fullObjectSelect = true
|
||||
q.select('data')
|
||||
}
|
||||
|
||||
q.select('id')
|
||||
q.select('createdAt')
|
||||
q.select('speckleType')
|
||||
q.select('totalChildrenCount')
|
||||
|
||||
q.from('object_children_closure')
|
||||
|
||||
q.rightJoin('objects', function () {
|
||||
this.on('objects.streamId', '=', 'object_children_closure.streamId').andOn(
|
||||
'objects.id',
|
||||
'=',
|
||||
'object_children_closure.child'
|
||||
)
|
||||
})
|
||||
.where(
|
||||
knex.raw('object_children_closure."streamId" = ? AND parent = ?', [
|
||||
streamId,
|
||||
objectId
|
||||
])
|
||||
)
|
||||
.andWhere(knex.raw('object_children_closure.mindepth < ?', [depth]))
|
||||
.andWhere(knex.raw('id > ?', [cursor ? cursor : '0']))
|
||||
.orderBy('objects.id')
|
||||
.limit(limit)
|
||||
|
||||
const rows = await q
|
||||
|
||||
if (rows.length === 0) {
|
||||
return { objects: rows, cursor: null }
|
||||
}
|
||||
|
||||
if (!fullObjectSelect)
|
||||
rows.forEach((o, i, arr) => {
|
||||
const no = {
|
||||
id: o.id,
|
||||
createdAt: o.createdAt,
|
||||
speckleType: o.speckleType,
|
||||
totalChildrenCount: o.totalChildrenCount,
|
||||
data: {}
|
||||
}
|
||||
let k = 0
|
||||
for (const field of select || []) {
|
||||
set(no.data, field, o[k++])
|
||||
}
|
||||
arr[i] = no
|
||||
})
|
||||
|
||||
const lastId = rows[rows.length - 1].id
|
||||
return { objects: rows, cursor: lastId }
|
||||
}
|
||||
|
|
|
@ -5,91 +5,6 @@ const knex = require(`@/db/knex`)
|
|||
const Objects = () => knex('objects')
|
||||
|
||||
module.exports = {
|
||||
/**
|
||||
* @returns {Promise<{objects: import('@/modules/core/helpers/types').ObjectRecord[], cursor: string | null}>}
|
||||
*/
|
||||
async getObjectChildren({ streamId, objectId, limit, depth, select, cursor }) {
|
||||
limit = parseInt(limit) || 50
|
||||
depth = parseInt(depth) || 1000
|
||||
|
||||
let fullObjectSelect = false
|
||||
|
||||
const q = knex.with(
|
||||
'object_children_closure',
|
||||
knex.raw(
|
||||
`SELECT objects.id as parent, d.key as child, d.value as mindepth, ? as "streamId"
|
||||
FROM objects
|
||||
JOIN jsonb_each_text(objects.data->'__closure') d ON true
|
||||
where objects.id = ?`,
|
||||
[streamId, objectId]
|
||||
)
|
||||
)
|
||||
|
||||
if (Array.isArray(select)) {
|
||||
select.forEach((field, index) => {
|
||||
q.select(
|
||||
knex.raw('jsonb_path_query(data, :path) as :name:', {
|
||||
path: '$.' + field,
|
||||
name: '' + index
|
||||
})
|
||||
)
|
||||
})
|
||||
} else {
|
||||
fullObjectSelect = true
|
||||
q.select('data')
|
||||
}
|
||||
|
||||
q.select('id')
|
||||
q.select('createdAt')
|
||||
q.select('speckleType')
|
||||
q.select('totalChildrenCount')
|
||||
|
||||
q.from('object_children_closure')
|
||||
|
||||
q.rightJoin('objects', function () {
|
||||
this.on('objects.streamId', '=', 'object_children_closure.streamId').andOn(
|
||||
'objects.id',
|
||||
'=',
|
||||
'object_children_closure.child'
|
||||
)
|
||||
})
|
||||
.where(
|
||||
knex.raw('object_children_closure."streamId" = ? AND parent = ?', [
|
||||
streamId,
|
||||
objectId
|
||||
])
|
||||
)
|
||||
.andWhere(knex.raw('object_children_closure.mindepth < ?', [depth]))
|
||||
.andWhere(knex.raw('id > ?', [cursor ? cursor : '0']))
|
||||
.orderBy('objects.id')
|
||||
.limit(limit)
|
||||
|
||||
const rows = await q
|
||||
|
||||
if (rows.length === 0) {
|
||||
return { objects: rows, cursor: null }
|
||||
}
|
||||
|
||||
if (!fullObjectSelect)
|
||||
rows.forEach((o, i, arr) => {
|
||||
const no = {
|
||||
id: o.id,
|
||||
createdAt: o.createdAt,
|
||||
speckleType: o.speckleType,
|
||||
totalChildrenCount: o.totalChildrenCount,
|
||||
data: {}
|
||||
}
|
||||
let k = 0
|
||||
for (const field of select) {
|
||||
set(no.data, field, o[k++])
|
||||
}
|
||||
arr[i] = no
|
||||
})
|
||||
|
||||
const lastId = rows[rows.length - 1].id
|
||||
return { objects: rows, cursor: lastId }
|
||||
},
|
||||
|
||||
/**
|
||||
*
|
||||
* This query is inefficient on larger sets (n * 10k objects) as we need to return the total count on an arbitrarily (user) defined selection of objects.
|
||||
|
|
|
@ -7,11 +7,7 @@ const { cloneDeep, times, random, padStart } = require('lodash')
|
|||
const { beforeEachContext } = require('@/test/hooks')
|
||||
const { getAnIdForThisOnePlease } = require('@/test/helpers')
|
||||
|
||||
const {
|
||||
getObjects,
|
||||
getObjectChildren,
|
||||
getObjectChildrenQuery
|
||||
} = require('../services/objects')
|
||||
const { getObjects, getObjectChildrenQuery } = require('../services/objects')
|
||||
const {
|
||||
getStreamFactory,
|
||||
createStreamFactory
|
||||
|
@ -86,7 +82,8 @@ const {
|
|||
storeClosuresIfNotFoundFactory,
|
||||
storeObjectsIfNotFoundFactory,
|
||||
getFormattedObjectFactory,
|
||||
getObjectChildrenStreamFactory
|
||||
getObjectChildrenStreamFactory,
|
||||
getObjectChildrenFactory
|
||||
} = require('@/modules/core/repositories/objects')
|
||||
|
||||
const sampleCommit = JSON.parse(`{
|
||||
|
@ -190,6 +187,7 @@ const createObjects = createObjectsFactory({
|
|||
})
|
||||
const getObject = getFormattedObjectFactory({ db })
|
||||
const getObjectChildrenStream = getObjectChildrenStreamFactory({ db })
|
||||
const getObjectChildren = getObjectChildrenFactory({ db })
|
||||
|
||||
describe('Objects @core-objects', () => {
|
||||
const userOne = {
|
||||
|
|
Загрузка…
Ссылка в новой задаче