mirror of
https://github.com/n8n-io/n8n.git
synced 2026-05-30 08:17:06 +02:00
refactor(core): Rename data-store to data-table in the BE (no-changelog) (#20424)
This commit is contained in:
parent
123a742685
commit
a2aca5e7a4
|
|
@ -1,5 +0,0 @@
|
|||
import { Z } from 'zod-class';
|
||||
|
||||
import { dataStoreCreateColumnSchema } from '../../schemas/data-store.schema';
|
||||
|
||||
export class AddDataStoreColumnDto extends Z.class(dataStoreCreateColumnSchema.shape) {}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
import { z } from 'zod';
|
||||
import { Z } from 'zod-class';
|
||||
|
||||
import {
|
||||
dataStoreColumnNameSchema,
|
||||
dataStoreColumnValueSchema,
|
||||
insertRowReturnType,
|
||||
} from '../../schemas/data-store.schema';
|
||||
|
||||
export class AddDataStoreRowsDto extends Z.class({
|
||||
data: z.array(z.record(dataStoreColumnNameSchema, dataStoreColumnValueSchema)),
|
||||
returnType: insertRowReturnType,
|
||||
}) {}
|
||||
|
|
@ -1,11 +0,0 @@
|
|||
import { Z } from 'zod-class';
|
||||
|
||||
import {
|
||||
dataStoreColumnNameSchema,
|
||||
dataStoreColumnTypeSchema,
|
||||
} from '../../schemas/data-store.schema';
|
||||
|
||||
export class CreateDataStoreColumnDto extends Z.class({
|
||||
name: dataStoreColumnNameSchema,
|
||||
type: dataStoreColumnTypeSchema,
|
||||
}) {}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
import { z } from 'zod';
|
||||
import { Z } from 'zod-class';
|
||||
|
||||
import { CreateDataStoreColumnDto } from './create-data-store-column.dto';
|
||||
import { dataStoreNameSchema } from '../../schemas/data-store.schema';
|
||||
|
||||
export class CreateDataStoreDto extends Z.class({
|
||||
name: dataStoreNameSchema,
|
||||
columns: z.array(CreateDataStoreColumnDto),
|
||||
}) {}
|
||||
|
|
@ -1,7 +0,0 @@
|
|||
import { Z } from 'zod-class';
|
||||
|
||||
import { dataStoreNameSchema } from '../../schemas/data-store.schema';
|
||||
|
||||
export class UpdateDataStoreDto extends Z.class({
|
||||
name: dataStoreNameSchema,
|
||||
}) {}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { Z } from 'zod-class';
|
||||
|
||||
import { dataTableCreateColumnSchema } from '../../schemas/data-table.schema';
|
||||
|
||||
export class AddDataTableColumnDto extends Z.class(dataTableCreateColumnSchema.shape) {}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
import { z } from 'zod';
|
||||
import { Z } from 'zod-class';
|
||||
|
||||
import {
|
||||
dataTableColumnNameSchema,
|
||||
dataTableColumnValueSchema,
|
||||
insertRowReturnType,
|
||||
} from '../../schemas/data-table.schema';
|
||||
|
||||
export class AddDataTableRowsDto extends Z.class({
|
||||
data: z.array(z.record(dataTableColumnNameSchema, dataTableColumnValueSchema)),
|
||||
returnType: insertRowReturnType,
|
||||
}) {}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
import { Z } from 'zod-class';
|
||||
|
||||
import {
|
||||
dataTableColumnNameSchema,
|
||||
dataTableColumnTypeSchema,
|
||||
} from '../../schemas/data-table.schema';
|
||||
|
||||
export class CreateDataTableColumnDto extends Z.class({
|
||||
name: dataTableColumnNameSchema,
|
||||
type: dataTableColumnTypeSchema,
|
||||
}) {}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
import { z } from 'zod';
|
||||
import { Z } from 'zod-class';
|
||||
|
||||
import { CreateDataTableColumnDto } from './create-data-table-column.dto';
|
||||
import { dataTableNameSchema } from '../../schemas/data-table.schema';
|
||||
|
||||
export class CreateDataTableDto extends Z.class({
|
||||
name: dataTableNameSchema,
|
||||
columns: z.array(CreateDataTableColumnDto),
|
||||
}) {}
|
||||
|
|
@ -2,8 +2,8 @@ import { jsonParse } from 'n8n-workflow';
|
|||
import { z } from 'zod';
|
||||
import { Z } from 'zod-class';
|
||||
|
||||
import { dataStoreColumnNameSchema } from '../../schemas/data-store.schema';
|
||||
import { dataTableFilterSchema } from '../../schemas/data-table-filter.schema';
|
||||
import { dataTableColumnNameSchema } from '../../schemas/data-table.schema';
|
||||
import { paginationSchema } from '../pagination/pagination.dto';
|
||||
|
||||
const filterValidator = z
|
||||
|
|
@ -51,7 +51,7 @@ const sortByValidator = z
|
|||
let [column, direction] = val.split(':');
|
||||
|
||||
try {
|
||||
column = dataStoreColumnNameSchema.parse(column);
|
||||
column = dataTableColumnNameSchema.parse(column);
|
||||
} catch {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
|
|
@ -74,7 +74,7 @@ const sortByValidator = z
|
|||
return [column, direction] as const;
|
||||
});
|
||||
|
||||
export class ListDataStoreContentQueryDto extends Z.class({
|
||||
export class ListDataTableContentQueryDto extends Z.class({
|
||||
take: paginationSchema.take.optional(),
|
||||
skip: paginationSchema.skip.optional(),
|
||||
filter: filterValidator.optional(),
|
||||
|
|
@ -15,7 +15,7 @@ const VALID_SORT_OPTIONS = [
|
|||
'sizeBytes:desc',
|
||||
] as const;
|
||||
|
||||
export type ListDataStoreQuerySortOptions = (typeof VALID_SORT_OPTIONS)[number];
|
||||
export type ListDataTableQuerySortOptions = (typeof VALID_SORT_OPTIONS)[number];
|
||||
|
||||
const FILTER_OPTIONS = {
|
||||
id: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
|
|
@ -63,7 +63,7 @@ const sortByValidator = z
|
|||
.enum(VALID_SORT_OPTIONS, { message: `sortBy must be one of: ${VALID_SORT_OPTIONS.join(', ')}` })
|
||||
.optional();
|
||||
|
||||
export class ListDataStoreQueryDto extends Z.class({
|
||||
export class ListDataTableQueryDto extends Z.class({
|
||||
...paginationSchema,
|
||||
filter: filterValidator,
|
||||
sortBy: sortByValidator,
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { z } from 'zod';
|
||||
import { Z } from 'zod-class';
|
||||
|
||||
export class MoveDataStoreColumnDto extends Z.class({
|
||||
export class MoveDataTableColumnDto extends Z.class({
|
||||
targetIndex: z.number().int().nonnegative(),
|
||||
}) {}
|
||||
|
|
@ -1,11 +1,11 @@
|
|||
import { z } from 'zod';
|
||||
import { Z } from 'zod-class';
|
||||
|
||||
import {
|
||||
dataStoreColumnNameSchema,
|
||||
dataStoreColumnValueSchema,
|
||||
} from '../../schemas/data-store.schema';
|
||||
import { dataTableFilterSchema } from '../../schemas/data-table-filter.schema';
|
||||
import {
|
||||
dataTableColumnNameSchema,
|
||||
dataTableColumnValueSchema,
|
||||
} from '../../schemas/data-table.schema';
|
||||
|
||||
const updateFilterSchema = dataTableFilterSchema.refine((filter) => filter.filters.length > 0, {
|
||||
message: 'filter must not be empty',
|
||||
|
|
@ -14,7 +14,7 @@ const updateFilterSchema = dataTableFilterSchema.refine((filter) => filter.filte
|
|||
const updateDataTableRowShape = {
|
||||
filter: updateFilterSchema,
|
||||
data: z
|
||||
.record(dataStoreColumnNameSchema, dataStoreColumnValueSchema)
|
||||
.record(dataTableColumnNameSchema, dataTableColumnValueSchema)
|
||||
.refine((obj) => Object.keys(obj).length > 0, {
|
||||
message: 'data must not be empty',
|
||||
}),
|
||||
|
|
@ -0,0 +1,7 @@
|
|||
import { Z } from 'zod-class';
|
||||
|
||||
import { dataTableNameSchema } from '../../schemas/data-table.schema';
|
||||
|
||||
export class UpdateDataTableDto extends Z.class({
|
||||
name: dataTableNameSchema,
|
||||
}) {}
|
||||
|
|
@ -1,20 +1,20 @@
|
|||
import { z } from 'zod';
|
||||
import { Z } from 'zod-class';
|
||||
|
||||
import {
|
||||
dataStoreColumnNameSchema,
|
||||
dataStoreColumnValueSchema,
|
||||
} from '../../schemas/data-store.schema';
|
||||
import { dataTableFilterSchema } from '../../schemas/data-table-filter.schema';
|
||||
import {
|
||||
dataTableColumnNameSchema,
|
||||
dataTableColumnValueSchema,
|
||||
} from '../../schemas/data-table.schema';
|
||||
|
||||
const upsertFilterSchema = dataTableFilterSchema.refine((filter) => filter.filters.length > 0, {
|
||||
message: 'filter must not be empty',
|
||||
});
|
||||
|
||||
const upsertDataStoreRowShape = {
|
||||
const upsertDataTableRowShape = {
|
||||
filter: upsertFilterSchema,
|
||||
data: z
|
||||
.record(dataStoreColumnNameSchema, dataStoreColumnValueSchema)
|
||||
.record(dataTableColumnNameSchema, dataTableColumnValueSchema)
|
||||
.refine((obj) => Object.keys(obj).length > 0, {
|
||||
message: 'data must not be empty',
|
||||
}),
|
||||
|
|
@ -22,4 +22,4 @@ const upsertDataStoreRowShape = {
|
|||
dryRun: z.boolean().optional().default(false),
|
||||
};
|
||||
|
||||
export class UpsertDataStoreRowDto extends Z.class(upsertDataStoreRowShape) {}
|
||||
export class UpsertDataTableRowDto extends Z.class(upsertDataTableRowShape) {}
|
||||
|
|
@ -86,14 +86,14 @@ export { RoleGetQueryDto } from './roles/role-get-query.dto';
|
|||
|
||||
export { OidcConfigDto } from './oidc/config.dto';
|
||||
|
||||
export { CreateDataStoreDto } from './data-store/create-data-store.dto';
|
||||
export { UpdateDataStoreDto } from './data-store/update-data-store.dto';
|
||||
export { UpdateDataTableRowDto } from './data-store/update-data-store-row.dto';
|
||||
export { DeleteDataTableRowsDto } from './data-store/delete-data-table-rows.dto';
|
||||
export { UpsertDataStoreRowDto } from './data-store/upsert-data-store-row.dto';
|
||||
export { ListDataStoreQueryDto } from './data-store/list-data-store-query.dto';
|
||||
export { ListDataStoreContentQueryDto } from './data-store/list-data-store-content-query.dto';
|
||||
export { CreateDataStoreColumnDto } from './data-store/create-data-store-column.dto';
|
||||
export { AddDataStoreRowsDto } from './data-store/add-data-store-rows.dto';
|
||||
export { AddDataStoreColumnDto } from './data-store/add-data-store-column.dto';
|
||||
export { MoveDataStoreColumnDto } from './data-store/move-data-store-column.dto';
|
||||
export { CreateDataTableDto } from './data-table/create-data-table.dto';
|
||||
export { UpdateDataTableDto } from './data-table/update-data-table.dto';
|
||||
export { UpdateDataTableRowDto } from './data-table/update-data-table-row.dto';
|
||||
export { DeleteDataTableRowsDto } from './data-table/delete-data-table-rows.dto';
|
||||
export { UpsertDataTableRowDto } from './data-table/upsert-data-table-row.dto';
|
||||
export { ListDataTableQueryDto } from './data-table/list-data-table-query.dto';
|
||||
export { ListDataTableContentQueryDto } from './data-table/list-data-table-content-query.dto';
|
||||
export { CreateDataTableColumnDto } from './data-table/create-data-table-column.dto';
|
||||
export { AddDataTableRowsDto } from './data-table/add-data-table-rows.dto';
|
||||
export { AddDataTableColumnDto } from './data-table/add-data-table-column.dto';
|
||||
export { MoveDataTableColumnDto } from './data-table/move-data-table-column.dto';
|
||||
|
|
|
|||
|
|
@ -50,17 +50,17 @@ export {
|
|||
} from './schemas/user.schema';
|
||||
|
||||
export {
|
||||
DATA_STORE_COLUMN_REGEX,
|
||||
DATA_STORE_COLUMN_MAX_LENGTH,
|
||||
DATA_STORE_COLUMN_ERROR_MESSAGE,
|
||||
type DataStore,
|
||||
type DataStoreColumn,
|
||||
type DataStoreCreateColumnSchema,
|
||||
type DataStoreListFilter,
|
||||
type DataStoreListOptions,
|
||||
DATA_TABLE_COLUMN_REGEX,
|
||||
DATA_TABLE_COLUMN_MAX_LENGTH,
|
||||
DATA_TABLE_COLUMN_ERROR_MESSAGE,
|
||||
type DataTable,
|
||||
type DataTableColumn,
|
||||
type DataTableCreateColumnSchema,
|
||||
type DataTableListFilter,
|
||||
type DataTableListOptions,
|
||||
dateTimeSchema,
|
||||
dataStoreColumnNameSchema,
|
||||
} from './schemas/data-store.schema';
|
||||
dataTableColumnNameSchema,
|
||||
} from './schemas/data-table.schema';
|
||||
|
||||
export type {
|
||||
DataTableFilter,
|
||||
|
|
|
|||
|
|
@ -1,67 +0,0 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import type { ListDataStoreQueryDto } from '../dto';
|
||||
|
||||
export const insertRowReturnType = z.union([z.literal('all'), z.literal('count'), z.literal('id')]);
|
||||
|
||||
export const dataStoreNameSchema = z.string().trim().min(1).max(128);
|
||||
export const dataStoreIdSchema = z.string().max(36);
|
||||
|
||||
// Postgres does not allow leading numbers or -
|
||||
export const DATA_STORE_COLUMN_REGEX = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
||||
export const DATA_STORE_COLUMN_MAX_LENGTH = 63; // Postgres has a maximum of 63 characters
|
||||
export const DATA_STORE_COLUMN_ERROR_MESSAGE =
|
||||
'Only alphabetical characters and non-leading numbers and underscores are allowed for column names, and the maximum length is 63 characters.';
|
||||
|
||||
export const dataStoreColumnNameSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(DATA_STORE_COLUMN_MAX_LENGTH) // Postgres has a maximum of 63 characters
|
||||
.regex(DATA_STORE_COLUMN_REGEX, DATA_STORE_COLUMN_ERROR_MESSAGE);
|
||||
export const dataStoreColumnTypeSchema = z.enum(['string', 'number', 'boolean', 'date']);
|
||||
|
||||
export const dataStoreCreateColumnSchema = z.object({
|
||||
name: dataStoreColumnNameSchema,
|
||||
type: dataStoreColumnTypeSchema,
|
||||
index: z.number().optional(),
|
||||
});
|
||||
export type DataStoreCreateColumnSchema = z.infer<typeof dataStoreCreateColumnSchema>;
|
||||
|
||||
export const dataStoreColumnSchema = dataStoreCreateColumnSchema.extend({
|
||||
dataStoreId: dataStoreIdSchema,
|
||||
});
|
||||
|
||||
export const dataStoreSchema = z.object({
|
||||
id: dataStoreIdSchema,
|
||||
name: dataStoreNameSchema,
|
||||
columns: z.array(dataStoreColumnSchema),
|
||||
createdAt: z.string().datetime(),
|
||||
updatedAt: z.string().datetime(),
|
||||
});
|
||||
export type DataStore = z.infer<typeof dataStoreSchema>;
|
||||
export type DataStoreColumn = z.infer<typeof dataStoreColumnSchema>;
|
||||
|
||||
export type DataStoreListFilter = {
|
||||
id?: string | string[];
|
||||
projectId?: string | string[];
|
||||
name?: string;
|
||||
};
|
||||
|
||||
export type DataStoreListOptions = Partial<ListDataStoreQueryDto> & {
|
||||
filter: { projectId: string };
|
||||
};
|
||||
|
||||
export const dateTimeSchema = z
|
||||
.string()
|
||||
.datetime({ offset: true })
|
||||
.transform((s) => new Date(s))
|
||||
.pipe(z.date());
|
||||
|
||||
export const dataStoreColumnValueSchema = z.union([
|
||||
z.string(),
|
||||
z.number(),
|
||||
z.boolean(),
|
||||
z.null(),
|
||||
z.date(),
|
||||
]);
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import { dataStoreColumnNameSchema } from './data-store.schema';
|
||||
import { dataTableColumnNameSchema } from './data-table.schema';
|
||||
|
||||
export const FilterConditionSchema = z.union([
|
||||
z.literal('eq'),
|
||||
|
|
@ -16,7 +16,7 @@ export const FilterConditionSchema = z.union([
|
|||
export type DataTableFilterConditionType = z.infer<typeof FilterConditionSchema>;
|
||||
|
||||
export const dataTableFilterRecordSchema = z.object({
|
||||
columnName: dataStoreColumnNameSchema,
|
||||
columnName: dataTableColumnNameSchema,
|
||||
condition: FilterConditionSchema.default('eq'),
|
||||
value: z.union([z.string(), z.number(), z.boolean(), z.date(), z.null()]),
|
||||
});
|
||||
|
|
|
|||
67
packages/@n8n/api-types/src/schemas/data-table.schema.ts
Normal file
67
packages/@n8n/api-types/src/schemas/data-table.schema.ts
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
import { z } from 'zod';
|
||||
|
||||
import type { ListDataTableQueryDto } from '../dto';
|
||||
|
||||
export const insertRowReturnType = z.union([z.literal('all'), z.literal('count'), z.literal('id')]);
|
||||
|
||||
export const dataTableNameSchema = z.string().trim().min(1).max(128);
|
||||
export const dataTableIdSchema = z.string().max(36);
|
||||
|
||||
// Postgres does not allow leading numbers or -
|
||||
export const DATA_TABLE_COLUMN_REGEX = /^[a-zA-Z][a-zA-Z0-9_]*$/;
|
||||
export const DATA_TABLE_COLUMN_MAX_LENGTH = 63; // Postgres has a maximum of 63 characters
|
||||
export const DATA_TABLE_COLUMN_ERROR_MESSAGE =
|
||||
'Only alphabetical characters and non-leading numbers and underscores are allowed for column names, and the maximum length is 63 characters.';
|
||||
|
||||
export const dataTableColumnNameSchema = z
|
||||
.string()
|
||||
.trim()
|
||||
.min(1)
|
||||
.max(DATA_TABLE_COLUMN_MAX_LENGTH) // Postgres has a maximum of 63 characters
|
||||
.regex(DATA_TABLE_COLUMN_REGEX, DATA_TABLE_COLUMN_ERROR_MESSAGE);
|
||||
export const dataTableColumnTypeSchema = z.enum(['string', 'number', 'boolean', 'date']);
|
||||
|
||||
export const dataTableCreateColumnSchema = z.object({
|
||||
name: dataTableColumnNameSchema,
|
||||
type: dataTableColumnTypeSchema,
|
||||
index: z.number().optional(),
|
||||
});
|
||||
export type DataTableCreateColumnSchema = z.infer<typeof dataTableCreateColumnSchema>;
|
||||
|
||||
export const dataTableColumnSchema = dataTableCreateColumnSchema.extend({
|
||||
dataTableId: dataTableIdSchema,
|
||||
});
|
||||
|
||||
export const dataTableSchema = z.object({
|
||||
id: dataTableIdSchema,
|
||||
name: dataTableNameSchema,
|
||||
columns: z.array(dataTableColumnSchema),
|
||||
createdAt: z.string().datetime(),
|
||||
updatedAt: z.string().datetime(),
|
||||
});
|
||||
export type DataTable = z.infer<typeof dataTableSchema>;
|
||||
export type DataTableColumn = z.infer<typeof dataTableColumnSchema>;
|
||||
|
||||
export type DataTableListFilter = {
|
||||
id?: string | string[];
|
||||
projectId?: string | string[];
|
||||
name?: string;
|
||||
};
|
||||
|
||||
export type DataTableListOptions = Partial<ListDataTableQueryDto> & {
|
||||
filter: { projectId: string };
|
||||
};
|
||||
|
||||
export const dateTimeSchema = z
|
||||
.string()
|
||||
.datetime({ offset: true })
|
||||
.transform((s) => new Date(s))
|
||||
.pipe(z.date());
|
||||
|
||||
export const dataTableColumnValueSchema = z.union([
|
||||
z.string(),
|
||||
z.number(),
|
||||
z.boolean(),
|
||||
z.null(),
|
||||
z.date(),
|
||||
]);
|
||||
|
|
@ -24,7 +24,7 @@ import {
|
|||
SubworkflowPolicyChecker,
|
||||
} from '@/executions/pre-execution-checks';
|
||||
import { ExternalHooks } from '@/external-hooks';
|
||||
import { DataStoreProxyService } from '@/modules/data-table/data-store-proxy.service';
|
||||
import { DataTableProxyService } from '@/modules/data-table/data-table-proxy.service';
|
||||
import { UrlService } from '@/services/url.service';
|
||||
import { WorkflowStatisticsService } from '@/services/workflow-statistics.service';
|
||||
import { Telemetry } from '@/telemetry';
|
||||
|
|
@ -99,7 +99,7 @@ describe('WorkflowExecuteAdditionalData', () => {
|
|||
mockInstance(CredentialsPermissionChecker);
|
||||
mockInstance(SubworkflowPolicyChecker);
|
||||
mockInstance(WorkflowStatisticsService);
|
||||
mockInstance(DataStoreProxyService);
|
||||
mockInstance(DataTableProxyService);
|
||||
|
||||
const urlService = mockInstance(UrlService);
|
||||
Container.set(UrlService, urlService);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { DataStore } from '@n8n/api-types';
|
||||
import type { DataTable } from '@n8n/api-types';
|
||||
import {
|
||||
createTeamProject,
|
||||
getPersonalProject,
|
||||
|
|
@ -8,7 +8,7 @@ import {
|
|||
import type { Project, User } from '@n8n/db';
|
||||
import { DateTime } from 'luxon';
|
||||
|
||||
import { createDataStore } from '@test-integration/db/data-stores';
|
||||
import { createDataTable } from '@test-integration/db/data-tables';
|
||||
import { createOwner, createMember, createAdmin } from '@test-integration/db/users';
|
||||
import type { SuperAgentTest } from '@test-integration/types';
|
||||
import * as utils from '@test-integration/utils';
|
||||
|
|
@ -51,67 +51,66 @@ afterAll(async () => {
|
|||
});
|
||||
|
||||
describe('GET /data-tables-global', () => {
|
||||
test('should not list data stores when no data stores exist', async () => {
|
||||
test('should not list data tables when no data tables exist', async () => {
|
||||
const response = await authOwnerAgent.get('/data-tables-global').expect(200);
|
||||
expect(response.body.data.count).toBe(0);
|
||||
expect(response.body.data.data).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('should not list data stores from projects member has no access to', async () => {
|
||||
test('should not list data tables from projects member has no access to', async () => {
|
||||
const project = await createTeamProject('test project', owner);
|
||||
await createDataStore(project, { name: 'Test Data Store' });
|
||||
|
||||
await createDataTable(project, { name: 'Test Data Table' });
|
||||
const response = await authMemberAgent.get('/data-tables-global').expect(200);
|
||||
expect(response.body.data.count).toBe(0);
|
||||
expect(response.body.data.data).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('should not list data stores from projects admin has no access to', async () => {
|
||||
test('should not list data tables from projects admin has no access to', async () => {
|
||||
const project = await createTeamProject('test project', owner);
|
||||
await createDataStore(project, { name: 'Test Data Store' });
|
||||
await createDataTable(project, { name: 'Test Data Table' });
|
||||
|
||||
const response = await authAdminAgent.get('/data-tables-global').expect(200);
|
||||
expect(response.body.data.count).toBe(0);
|
||||
expect(response.body.data.data).toHaveLength(0);
|
||||
});
|
||||
|
||||
test("should not list data stores from another user's personal project", async () => {
|
||||
await createDataStore(ownerProject, { name: 'Personal Data Store' });
|
||||
test("should not list data tables from another user's personal project", async () => {
|
||||
await createDataTable(ownerProject, { name: 'Personal Data Table' });
|
||||
|
||||
const response = await authAdminAgent.get('/data-tables-global').expect(200);
|
||||
expect(response.body.data.count).toBe(0);
|
||||
expect(response.body.data.data).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('should list data stores from team projects where user has project:viewer role', async () => {
|
||||
test('should list data tables from team projects where user has project:viewer role', async () => {
|
||||
const project = await createTeamProject('test project', owner);
|
||||
await linkUserToProject(member, project, 'project:viewer');
|
||||
await createDataStore(project, { name: 'Test Data Store' });
|
||||
await createDataTable(project, { name: 'Test Data Table' });
|
||||
|
||||
const response = await authMemberAgent.get('/data-tables-global').expect(200);
|
||||
|
||||
expect(response.body.data.count).toBe(1);
|
||||
expect(response.body.data.data).toHaveLength(1);
|
||||
expect(response.body.data.data[0].name).toBe('Test Data Store');
|
||||
expect(response.body.data.data[0].name).toBe('Test Data Table');
|
||||
});
|
||||
|
||||
test("should list data stores from user's own personal project", async () => {
|
||||
await createDataStore(ownerProject, { name: 'Personal Data Store 1' });
|
||||
await createDataStore(ownerProject, { name: 'Personal Data Store 2' });
|
||||
test("should list data tables from user's own personal project", async () => {
|
||||
await createDataTable(ownerProject, { name: 'Personal Data Table 1' });
|
||||
await createDataTable(ownerProject, { name: 'Personal Data Table 2' });
|
||||
|
||||
const response = await authOwnerAgent.get('/data-tables-global').expect(200);
|
||||
|
||||
expect(response.body.data.count).toBe(2);
|
||||
expect(response.body.data.data).toHaveLength(2);
|
||||
expect(response.body.data.data.map((f: DataStore) => f.name).sort()).toEqual(
|
||||
['Personal Data Store 1', 'Personal Data Store 2'].sort(),
|
||||
expect(response.body.data.data.map((f: DataTable) => f.name).sort()).toEqual(
|
||||
['Personal Data Table 1', 'Personal Data Table 2'].sort(),
|
||||
);
|
||||
});
|
||||
|
||||
test('should filter data stores by projectId', async () => {
|
||||
await createDataStore(ownerProject, { name: 'Test Data Store 1' });
|
||||
await createDataStore(ownerProject, { name: 'Test Data Store 2' });
|
||||
await createDataStore(memberProject, { name: 'Another Data Store' });
|
||||
test('should filter data tables by projectId', async () => {
|
||||
await createDataTable(ownerProject, { name: 'Test Data Table 1' });
|
||||
await createDataTable(ownerProject, { name: 'Test Data Table 2' });
|
||||
await createDataTable(memberProject, { name: 'Another Data Table' });
|
||||
|
||||
const response = await authOwnerAgent
|
||||
.get('/data-tables-global')
|
||||
|
|
@ -120,15 +119,15 @@ describe('GET /data-tables-global', () => {
|
|||
|
||||
expect(response.body.data.count).toBe(2);
|
||||
expect(response.body.data.data).toHaveLength(2);
|
||||
expect(response.body.data.data.map((f: DataStore) => f.name).sort()).toEqual(
|
||||
['Test Data Store 1', 'Test Data Store 2'].sort(),
|
||||
expect(response.body.data.data.map((f: DataTable) => f.name).sort()).toEqual(
|
||||
['Test Data Table 1', 'Test Data Table 2'].sort(),
|
||||
);
|
||||
});
|
||||
|
||||
test('should not list projects the user cant access even with project filters', async () => {
|
||||
await createDataStore(ownerProject, { name: 'Test Data Store 1' });
|
||||
await createDataStore(ownerProject, { name: 'Test Data Store 2' });
|
||||
await createDataStore(memberProject, { name: 'Another Data Store' });
|
||||
await createDataTable(ownerProject, { name: 'Test Data Table 1' });
|
||||
await createDataTable(ownerProject, { name: 'Test Data Table 2' });
|
||||
await createDataTable(memberProject, { name: 'Another Data Table' });
|
||||
|
||||
const response = await authMemberAgent
|
||||
.get('/data-tables-global')
|
||||
|
|
@ -139,12 +138,12 @@ describe('GET /data-tables-global', () => {
|
|||
expect(response.body.data.data).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('should filter data stores by name', async () => {
|
||||
test('should filter data tables by name', async () => {
|
||||
const project = await createTeamProject('test project', owner);
|
||||
|
||||
await createDataStore(ownerProject, { name: 'Test Data Store' });
|
||||
await createDataStore(ownerProject, { name: 'Another Data Store' });
|
||||
await createDataStore(project, { name: 'Test Something Else' });
|
||||
await createDataTable(ownerProject, { name: 'Test Data Table' });
|
||||
await createDataTable(ownerProject, { name: 'Another Data Table' });
|
||||
await createDataTable(project, { name: 'Test Something Else' });
|
||||
|
||||
const response = await authOwnerAgent
|
||||
.get('/data-tables-global')
|
||||
|
|
@ -154,14 +153,14 @@ describe('GET /data-tables-global', () => {
|
|||
expect(response.body.data.count).toBe(2);
|
||||
expect(response.body.data.data).toHaveLength(2);
|
||||
expect(response.body.data.data.map((f: any) => f.name).sort()).toEqual(
|
||||
['Test Data Store', 'Test Something Else'].sort(),
|
||||
['Test Data Table', 'Test Something Else'].sort(),
|
||||
);
|
||||
});
|
||||
|
||||
test('should filter data stores by id', async () => {
|
||||
const dataStore1 = await createDataStore(ownerProject, { name: 'Data Store 1' });
|
||||
await createDataStore(ownerProject, { name: 'Data Store 2' });
|
||||
await createDataStore(ownerProject, { name: 'Data Store 3' });
|
||||
test('should filter data tables by id', async () => {
|
||||
const dataStore1 = await createDataTable(ownerProject, { name: 'Data Table 1' });
|
||||
await createDataTable(ownerProject, { name: 'Data Table 2' });
|
||||
await createDataTable(ownerProject, { name: 'Data Table 3' });
|
||||
|
||||
const response = await authOwnerAgent
|
||||
.get('/data-tables-global')
|
||||
|
|
@ -170,15 +169,15 @@ describe('GET /data-tables-global', () => {
|
|||
|
||||
expect(response.body.data.count).toBe(1);
|
||||
expect(response.body.data.data).toHaveLength(1);
|
||||
expect(response.body.data.data[0].name).toBe('Data Store 1');
|
||||
expect(response.body.data.data[0].name).toBe('Data Table 1');
|
||||
});
|
||||
|
||||
test('should filter data stores by multiple names (AND operator)', async () => {
|
||||
test('should filter data tables by multiple names (AND operator)', async () => {
|
||||
const project = await createTeamProject('test project', owner);
|
||||
|
||||
await createDataStore(ownerProject, { name: 'Data Store' });
|
||||
await createDataStore(ownerProject, { name: 'Test Store' });
|
||||
await createDataStore(project, { name: 'Another Store' });
|
||||
await createDataTable(ownerProject, { name: 'Data Table' });
|
||||
await createDataTable(ownerProject, { name: 'Test Store' });
|
||||
await createDataTable(project, { name: 'Another Store' });
|
||||
|
||||
const response = await authOwnerAgent
|
||||
.get('/data-tables-global')
|
||||
|
|
@ -193,8 +192,8 @@ describe('GET /data-tables-global', () => {
|
|||
test('should apply pagination with take parameter', async () => {
|
||||
const project = await createTeamProject('test project', owner);
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
await createDataStore(i % 2 ? ownerProject : project, {
|
||||
name: `Data Store ${i}`,
|
||||
await createDataTable(i % 2 ? ownerProject : project, {
|
||||
name: `Data Table ${i}`,
|
||||
updatedAt: DateTime.now()
|
||||
.minus({ minutes: 6 - i })
|
||||
.toJSDate(),
|
||||
|
|
@ -205,18 +204,18 @@ describe('GET /data-tables-global', () => {
|
|||
|
||||
expect(response.body.data.count).toBe(5); // Total count should be 5
|
||||
expect(response.body.data.data).toHaveLength(3); // But only 3 returned
|
||||
expect(response.body.data.data.map((store: DataStore) => store.name)).toEqual([
|
||||
'Data Store 5',
|
||||
'Data Store 4',
|
||||
'Data Store 3',
|
||||
expect(response.body.data.data.map((store: DataTable) => store.name)).toEqual([
|
||||
'Data Table 5',
|
||||
'Data Table 4',
|
||||
'Data Table 3',
|
||||
]);
|
||||
});
|
||||
|
||||
test('should apply pagination with skip parameter', async () => {
|
||||
const project = await createTeamProject('test project', owner);
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
await createDataStore(i % 2 ? ownerProject : project, {
|
||||
name: `Data Store ${i}`,
|
||||
await createDataTable(i % 2 ? ownerProject : project, {
|
||||
name: `Data Table ${i}`,
|
||||
updatedAt: DateTime.now()
|
||||
.minus({ minutes: 6 - i })
|
||||
.toJSDate(),
|
||||
|
|
@ -227,18 +226,18 @@ describe('GET /data-tables-global', () => {
|
|||
|
||||
expect(response.body.data.count).toBe(5);
|
||||
expect(response.body.data.data).toHaveLength(3);
|
||||
expect(response.body.data.data.map((store: DataStore) => store.name)).toEqual([
|
||||
'Data Store 3',
|
||||
'Data Store 2',
|
||||
'Data Store 1',
|
||||
expect(response.body.data.data.map((store: DataTable) => store.name)).toEqual([
|
||||
'Data Table 3',
|
||||
'Data Table 2',
|
||||
'Data Table 1',
|
||||
]);
|
||||
});
|
||||
|
||||
test('should apply combined skip and take parameters', async () => {
|
||||
const project = await createTeamProject('test project', owner);
|
||||
for (let i = 1; i <= 5; i++) {
|
||||
await createDataStore(i % 2 ? ownerProject : project, {
|
||||
name: `Data Store ${i}`,
|
||||
await createDataTable(i % 2 ? ownerProject : project, {
|
||||
name: `Data Table ${i}`,
|
||||
updatedAt: DateTime.now()
|
||||
.minus({ minutes: 6 - i })
|
||||
.toJSDate(),
|
||||
|
|
@ -252,57 +251,57 @@ describe('GET /data-tables-global', () => {
|
|||
|
||||
expect(response.body.data.count).toBe(5);
|
||||
expect(response.body.data.data).toHaveLength(2);
|
||||
expect(response.body.data.data.map((store: DataStore) => store.name)).toEqual([
|
||||
'Data Store 4',
|
||||
'Data Store 3',
|
||||
expect(response.body.data.data.map((store: DataTable) => store.name)).toEqual([
|
||||
'Data Table 4',
|
||||
'Data Table 3',
|
||||
]);
|
||||
});
|
||||
|
||||
test('should sort data stores by name ascending', async () => {
|
||||
await createDataStore(ownerProject, { name: 'Z Data Store' });
|
||||
await createDataStore(ownerProject, { name: 'A Data Store' });
|
||||
await createDataStore(ownerProject, { name: 'M Data Store' });
|
||||
test('should sort data tables by name ascending', async () => {
|
||||
await createDataTable(ownerProject, { name: 'Z Data Table' });
|
||||
await createDataTable(ownerProject, { name: 'A Data Table' });
|
||||
await createDataTable(ownerProject, { name: 'M Data Table' });
|
||||
|
||||
const response = await authOwnerAgent
|
||||
.get('/data-tables-global')
|
||||
.query({ sortBy: 'name:asc' })
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.data.data.map((store: DataStore) => store.name)).toEqual([
|
||||
'A Data Store',
|
||||
'M Data Store',
|
||||
'Z Data Store',
|
||||
expect(response.body.data.data.map((store: DataTable) => store.name)).toEqual([
|
||||
'A Data Table',
|
||||
'M Data Table',
|
||||
'Z Data Table',
|
||||
]);
|
||||
});
|
||||
|
||||
test('should sort data stores by name descending', async () => {
|
||||
await createDataStore(ownerProject, { name: 'Z Data Store' });
|
||||
await createDataStore(ownerProject, { name: 'A Data Store' });
|
||||
await createDataStore(ownerProject, { name: 'M Data Store' });
|
||||
test('should sort data tables by name descending', async () => {
|
||||
await createDataTable(ownerProject, { name: 'Z Data Table' });
|
||||
await createDataTable(ownerProject, { name: 'A Data Table' });
|
||||
await createDataTable(ownerProject, { name: 'M Data Table' });
|
||||
|
||||
const response = await authOwnerAgent
|
||||
.get('/data-tables-global')
|
||||
.query({ sortBy: 'name:desc' })
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.data.data.map((f: DataStore) => f.name)).toEqual([
|
||||
'Z Data Store',
|
||||
'M Data Store',
|
||||
'A Data Store',
|
||||
expect(response.body.data.data.map((f: DataTable) => f.name)).toEqual([
|
||||
'Z Data Table',
|
||||
'M Data Table',
|
||||
'A Data Table',
|
||||
]);
|
||||
});
|
||||
|
||||
test('should sort data stores by updatedAt', async () => {
|
||||
await createDataStore(ownerProject, {
|
||||
name: 'Older Data Store',
|
||||
test('should sort data tables by updatedAt', async () => {
|
||||
await createDataTable(ownerProject, {
|
||||
name: 'Older Data Table',
|
||||
updatedAt: DateTime.now().minus({ days: 2 }).toJSDate(),
|
||||
});
|
||||
await createDataStore(ownerProject, {
|
||||
name: 'Newest Data Store',
|
||||
await createDataTable(ownerProject, {
|
||||
name: 'Newest Data Table',
|
||||
updatedAt: DateTime.now().toJSDate(),
|
||||
});
|
||||
await createDataStore(ownerProject, {
|
||||
name: 'Middle Data Store',
|
||||
await createDataTable(ownerProject, {
|
||||
name: 'Middle Data Table',
|
||||
updatedAt: DateTime.now().minus({ days: 1 }).toJSDate(),
|
||||
});
|
||||
|
||||
|
|
@ -311,16 +310,16 @@ describe('GET /data-tables-global', () => {
|
|||
.query({ sortBy: 'updatedAt:desc' })
|
||||
.expect(200);
|
||||
|
||||
expect(response.body.data.data.map((f: DataStore) => f.name)).toEqual([
|
||||
'Newest Data Store',
|
||||
'Middle Data Store',
|
||||
'Older Data Store',
|
||||
expect(response.body.data.data.map((f: DataTable) => f.name)).toEqual([
|
||||
'Newest Data Table',
|
||||
'Middle Data Table',
|
||||
'Older Data Table',
|
||||
]);
|
||||
});
|
||||
|
||||
test('should combine multiple query parameters correctly', async () => {
|
||||
const dataStore1 = await createDataStore(ownerProject, { name: 'Test Data Store' });
|
||||
await createDataStore(ownerProject, { name: 'Another Data Store' });
|
||||
const dataStore1 = await createDataTable(ownerProject, { name: 'Test Data Table' });
|
||||
await createDataTable(ownerProject, { name: 'Another Data Table' });
|
||||
|
||||
const response = await authOwnerAgent
|
||||
.get('/data-tables-global')
|
||||
|
|
@ -329,12 +328,12 @@ describe('GET /data-tables-global', () => {
|
|||
|
||||
expect(response.body.data.count).toBe(1);
|
||||
expect(response.body.data.data).toHaveLength(1);
|
||||
expect(response.body.data.data[0].name).toBe('Test Data Store');
|
||||
expect(response.body.data.data[0].name).toBe('Test Data Table');
|
||||
});
|
||||
|
||||
test('should include columns', async () => {
|
||||
await createDataStore(ownerProject, {
|
||||
name: 'Test Data Store',
|
||||
await createDataTable(ownerProject, {
|
||||
name: 'Test Data Table',
|
||||
columns: [
|
||||
{
|
||||
name: 'test_column_1',
|
||||
|
|
|
|||
|
|
@ -14,8 +14,8 @@ import { mock } from 'jest-mock-extended';
|
|||
|
||||
import { createUser } from '@test-integration/db/users';
|
||||
|
||||
import { DataStoreAggregateService } from '../data-store-aggregate.service';
|
||||
import { DataStoreService } from '../data-store.service';
|
||||
import { DataTableAggregateService } from '../data-table-aggregate.service';
|
||||
import { DataTableService } from '../data-table.service';
|
||||
|
||||
beforeAll(async () => {
|
||||
await testModules.loadModules(['data-table']);
|
||||
|
|
@ -31,15 +31,15 @@ afterAll(async () => {
|
|||
});
|
||||
|
||||
describe('dataStoreAggregate', () => {
|
||||
let dataStoreService: DataStoreService;
|
||||
let dataStoreAggregateService: DataStoreAggregateService;
|
||||
let dataStoreService: DataTableService;
|
||||
let dataStoreAggregateService: DataTableAggregateService;
|
||||
const manager = mock<EntityManager>();
|
||||
const projectRelationRepository = mock<ProjectRelationRepository>({ manager });
|
||||
|
||||
beforeAll(() => {
|
||||
Container.set(ProjectRelationRepository, projectRelationRepository);
|
||||
dataStoreAggregateService = Container.get(DataStoreAggregateService);
|
||||
dataStoreService = Container.get(DataStoreService);
|
||||
dataStoreAggregateService = Container.get(DataTableAggregateService);
|
||||
dataStoreService = Container.get(DataTableService);
|
||||
});
|
||||
|
||||
let user: User;
|
||||
|
|
@ -53,18 +53,18 @@ describe('dataStoreAggregate', () => {
|
|||
});
|
||||
|
||||
afterEach(async () => {
|
||||
// Clean up any created user data stores
|
||||
await dataStoreService.deleteDataStoreAll();
|
||||
// Clean up any created user data tables
|
||||
await dataStoreService.deleteDataTableAll();
|
||||
});
|
||||
|
||||
describe('getManyAndCount', () => {
|
||||
it('should return the correct data stores for the user', async () => {
|
||||
it('should return the correct data tables for the user', async () => {
|
||||
// ARRANGE
|
||||
const ds1 = await dataStoreService.createDataStore(project1.id, {
|
||||
const ds1 = await dataStoreService.createDataTable(project1.id, {
|
||||
name: 'store1',
|
||||
columns: [],
|
||||
});
|
||||
const ds2 = await dataStoreService.createDataStore(project1.id, {
|
||||
const ds2 = await dataStoreService.createDataTable(project1.id, {
|
||||
name: 'store2',
|
||||
columns: [],
|
||||
});
|
||||
|
|
@ -92,7 +92,7 @@ describe('dataStoreAggregate', () => {
|
|||
},
|
||||
]);
|
||||
|
||||
await dataStoreService.createDataStore(project2.id, {
|
||||
await dataStoreService.createDataTable(project2.id, {
|
||||
name: 'store3',
|
||||
columns: [],
|
||||
});
|
||||
|
|
@ -118,7 +118,7 @@ describe('dataStoreAggregate', () => {
|
|||
// ARRANGE
|
||||
const currentUser = await createUser({ role: GLOBAL_MEMBER_ROLE });
|
||||
|
||||
await dataStoreService.createDataStore(project1.id, {
|
||||
await dataStoreService.createDataTable(project1.id, {
|
||||
name: 'store1',
|
||||
columns: [],
|
||||
});
|
||||
|
|
@ -135,13 +135,13 @@ describe('dataStoreAggregate', () => {
|
|||
expect(result.count).toBe(0);
|
||||
});
|
||||
|
||||
it('should return only the data store matching the given data store id filter', async () => {
|
||||
it('should return only the data table matching the given data table id filter', async () => {
|
||||
// ARRANGE
|
||||
await dataStoreService.createDataStore(project1.id, {
|
||||
await dataStoreService.createDataTable(project1.id, {
|
||||
name: 'store1',
|
||||
columns: [],
|
||||
});
|
||||
const ds2 = await dataStoreService.createDataStore(project1.id, {
|
||||
const ds2 = await dataStoreService.createDataTable(project1.id, {
|
||||
name: 'store2',
|
||||
columns: [],
|
||||
});
|
||||
|
|
@ -182,15 +182,15 @@ describe('dataStoreAggregate', () => {
|
|||
|
||||
it('should respect pagination (skip/take)', async () => {
|
||||
// ARRANGE
|
||||
const ds1 = await dataStoreService.createDataStore(project1.id, {
|
||||
const ds1 = await dataStoreService.createDataTable(project1.id, {
|
||||
name: 'store1',
|
||||
columns: [],
|
||||
});
|
||||
const ds2 = await dataStoreService.createDataStore(project1.id, {
|
||||
const ds2 = await dataStoreService.createDataTable(project1.id, {
|
||||
name: 'store2',
|
||||
columns: [],
|
||||
});
|
||||
const ds3 = await dataStoreService.createDataStore(project1.id, {
|
||||
const ds3 = await dataStoreService.createDataTable(project1.id, {
|
||||
name: 'store3',
|
||||
columns: [],
|
||||
});
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
|
|
@ -3,18 +3,18 @@ import { testDb, testModules } from '@n8n/backend-test-utils';
|
|||
import type { Project } from '@n8n/db';
|
||||
import { mock } from 'jest-mock-extended';
|
||||
import type {
|
||||
AddDataStoreColumnOptions,
|
||||
AddDataTableColumnOptions,
|
||||
INode,
|
||||
ListDataStoreRowsOptions,
|
||||
MoveDataStoreColumnOptions,
|
||||
UpsertDataStoreRowOptions,
|
||||
ListDataTableRowsOptions,
|
||||
MoveDataTableColumnOptions,
|
||||
UpsertDataTableRowOptions,
|
||||
Workflow,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import type { OwnershipService } from '@/services/ownership.service';
|
||||
|
||||
import { DataStoreProxyService } from '../data-store-proxy.service';
|
||||
import type { DataStoreService } from '../data-store.service';
|
||||
import { DataTableProxyService } from '../data-table-proxy.service';
|
||||
import type { DataTableService } from '../data-table.service';
|
||||
|
||||
const PROJECT_ID = 'project-id';
|
||||
|
||||
|
|
@ -22,23 +22,23 @@ beforeAll(async () => {
|
|||
await testModules.loadModules(['data-table']);
|
||||
await testDb.init();
|
||||
});
|
||||
describe('DataStoreProxyService', () => {
|
||||
let dataStoreServiceMock = mock<DataStoreService>();
|
||||
describe('DataTableProxyService', () => {
|
||||
let dataTableServiceMock = mock<DataTableService>();
|
||||
let ownershipServiceMock = mock<OwnershipService>();
|
||||
let loggerMock = mock<Logger>();
|
||||
let dataStoreProxyService: DataStoreProxyService;
|
||||
let dataStoreProxyService: DataTableProxyService;
|
||||
|
||||
let workflow: Workflow;
|
||||
let node: INode;
|
||||
let project: Project;
|
||||
|
||||
beforeEach(() => {
|
||||
dataStoreServiceMock = mock<DataStoreService>();
|
||||
dataTableServiceMock = mock<DataTableService>();
|
||||
ownershipServiceMock = mock<OwnershipService>();
|
||||
loggerMock = mock<Logger>();
|
||||
|
||||
dataStoreProxyService = new DataStoreProxyService(
|
||||
dataStoreServiceMock,
|
||||
dataStoreProxyService = new DataTableProxyService(
|
||||
dataTableServiceMock,
|
||||
ownershipServiceMock,
|
||||
loggerMock,
|
||||
);
|
||||
|
|
@ -60,103 +60,103 @@ describe('DataStoreProxyService', () => {
|
|||
it('should call getManyAndCount with correct parameters', async () => {
|
||||
const options = { filter: { name: 'test' } };
|
||||
|
||||
const aggregateOperations = await dataStoreProxyService.getDataStoreAggregateProxy(
|
||||
const aggregateOperations = await dataStoreProxyService.getDataTableAggregateProxy(
|
||||
workflow,
|
||||
node,
|
||||
);
|
||||
await aggregateOperations.getManyAndCount(options);
|
||||
|
||||
expect(dataStoreServiceMock.getManyAndCount).toBeCalledWith({
|
||||
expect(dataTableServiceMock.getManyAndCount).toBeCalledWith({
|
||||
filter: { name: 'test', projectId: PROJECT_ID },
|
||||
});
|
||||
});
|
||||
|
||||
it('should call createDataStore with correct parameters', async () => {
|
||||
const options = { name: 'newDataStore', columns: [] };
|
||||
it('should call createDataTable with correct parameters', async () => {
|
||||
const options = { name: 'newDataTable', columns: [] };
|
||||
|
||||
const aggregateOperations = await dataStoreProxyService.getDataStoreAggregateProxy(
|
||||
const aggregateOperations = await dataStoreProxyService.getDataTableAggregateProxy(
|
||||
workflow,
|
||||
node,
|
||||
);
|
||||
await aggregateOperations.createDataStore(options);
|
||||
await aggregateOperations.createDataTable(options);
|
||||
|
||||
expect(dataStoreServiceMock.createDataStore).toBeCalledWith(PROJECT_ID, options);
|
||||
expect(dataTableServiceMock.createDataTable).toBeCalledWith(PROJECT_ID, options);
|
||||
});
|
||||
|
||||
it('should call deleteDataStoreByProject when proxy calls deleteDataStoreAll', async () => {
|
||||
const aggregateOperations = await dataStoreProxyService.getDataStoreAggregateProxy(
|
||||
it('should call deleteDataTableByProject when proxy calls deleteDataTableAll', async () => {
|
||||
const aggregateOperations = await dataStoreProxyService.getDataTableAggregateProxy(
|
||||
workflow,
|
||||
node,
|
||||
);
|
||||
await aggregateOperations.deleteDataStoreAll();
|
||||
await aggregateOperations.deleteDataTableAll();
|
||||
|
||||
expect(dataStoreServiceMock.deleteDataStoreByProjectId).toBeCalledWith(PROJECT_ID);
|
||||
expect(dataTableServiceMock.deleteDataTableByProjectId).toBeCalledWith(PROJECT_ID);
|
||||
});
|
||||
});
|
||||
it('should call updateDataStore with correct parameters', async () => {
|
||||
const options = { name: 'updatedDataStore' };
|
||||
it('should call updateDataTable with correct parameters', async () => {
|
||||
const options = { name: 'updatedDataTable' };
|
||||
|
||||
const dataStoreOperations = await dataStoreProxyService.getDataStoreProxy(
|
||||
const dataStoreOperations = await dataStoreProxyService.getDataTableProxy(
|
||||
workflow,
|
||||
node,
|
||||
'dataStore-id',
|
||||
);
|
||||
await dataStoreOperations.updateDataStore(options);
|
||||
await dataStoreOperations.updateDataTable(options);
|
||||
|
||||
expect(dataStoreServiceMock.updateDataStore).toBeCalledWith(
|
||||
expect(dataTableServiceMock.updateDataTable).toBeCalledWith(
|
||||
'dataStore-id',
|
||||
PROJECT_ID,
|
||||
options,
|
||||
);
|
||||
});
|
||||
|
||||
it('should call deleteDataStore with correct parameters', async () => {
|
||||
const dataStoreOperations = await dataStoreProxyService.getDataStoreProxy(
|
||||
it('should call deleteDataTable with correct parameters', async () => {
|
||||
const dataStoreOperations = await dataStoreProxyService.getDataTableProxy(
|
||||
workflow,
|
||||
node,
|
||||
'dataStore-id',
|
||||
);
|
||||
await dataStoreOperations.deleteDataStore();
|
||||
await dataStoreOperations.deleteDataTable();
|
||||
|
||||
expect(dataStoreServiceMock.deleteDataStore).toBeCalledWith('dataStore-id', PROJECT_ID);
|
||||
expect(dataTableServiceMock.deleteDataTable).toBeCalledWith('dataStore-id', PROJECT_ID);
|
||||
});
|
||||
|
||||
it('should call getColumns with correct parameters', async () => {
|
||||
const dataStoreOperations = await dataStoreProxyService.getDataStoreProxy(
|
||||
const dataStoreOperations = await dataStoreProxyService.getDataTableProxy(
|
||||
workflow,
|
||||
node,
|
||||
'dataStore-id',
|
||||
);
|
||||
await dataStoreOperations.getColumns();
|
||||
|
||||
expect(dataStoreServiceMock.getColumns).toBeCalledWith('dataStore-id', PROJECT_ID);
|
||||
expect(dataTableServiceMock.getColumns).toBeCalledWith('dataStore-id', PROJECT_ID);
|
||||
});
|
||||
|
||||
it('should call addColumn with correct parameters', async () => {
|
||||
const options: AddDataStoreColumnOptions = { name: 'newColumn', type: 'string' };
|
||||
const options: AddDataTableColumnOptions = { name: 'newColumn', type: 'string' };
|
||||
|
||||
const dataStoreOperations = await dataStoreProxyService.getDataStoreProxy(
|
||||
const dataStoreOperations = await dataStoreProxyService.getDataTableProxy(
|
||||
workflow,
|
||||
node,
|
||||
'dataStore-id',
|
||||
);
|
||||
await dataStoreOperations.addColumn(options);
|
||||
|
||||
expect(dataStoreServiceMock.addColumn).toBeCalledWith('dataStore-id', PROJECT_ID, options);
|
||||
expect(dataTableServiceMock.addColumn).toBeCalledWith('dataStore-id', PROJECT_ID, options);
|
||||
});
|
||||
|
||||
it('should call moveColumn with correct parameters', async () => {
|
||||
const columnId = 'column-id';
|
||||
const options: MoveDataStoreColumnOptions = { targetIndex: 1 };
|
||||
const options: MoveDataTableColumnOptions = { targetIndex: 1 };
|
||||
|
||||
const dataStoreOperations = await dataStoreProxyService.getDataStoreProxy(
|
||||
const dataStoreOperations = await dataStoreProxyService.getDataTableProxy(
|
||||
workflow,
|
||||
node,
|
||||
'dataStore-id',
|
||||
);
|
||||
await dataStoreOperations.moveColumn(columnId, options);
|
||||
|
||||
expect(dataStoreServiceMock.moveColumn).toBeCalledWith(
|
||||
expect(dataTableServiceMock.moveColumn).toBeCalledWith(
|
||||
'dataStore-id',
|
||||
PROJECT_ID,
|
||||
columnId,
|
||||
|
|
@ -167,32 +167,32 @@ describe('DataStoreProxyService', () => {
|
|||
it('should call deleteColumn with correct parameters', async () => {
|
||||
const columnId = 'column-id';
|
||||
|
||||
const dataStoreOperations = await dataStoreProxyService.getDataStoreProxy(
|
||||
const dataStoreOperations = await dataStoreProxyService.getDataTableProxy(
|
||||
workflow,
|
||||
node,
|
||||
'dataStore-id',
|
||||
);
|
||||
await dataStoreOperations.deleteColumn(columnId);
|
||||
|
||||
expect(dataStoreServiceMock.deleteColumn).toBeCalledWith('dataStore-id', PROJECT_ID, columnId);
|
||||
expect(dataTableServiceMock.deleteColumn).toBeCalledWith('dataStore-id', PROJECT_ID, columnId);
|
||||
});
|
||||
|
||||
it('should call getManyRowsAndCount with correct parameters', async () => {
|
||||
const options: ListDataStoreRowsOptions = {
|
||||
const options: ListDataTableRowsOptions = {
|
||||
filter: {
|
||||
filters: [{ columnName: 'x', condition: 'eq', value: 'testRow' }],
|
||||
type: 'and',
|
||||
},
|
||||
};
|
||||
|
||||
const dataStoreOperations = await dataStoreProxyService.getDataStoreProxy(
|
||||
const dataStoreOperations = await dataStoreProxyService.getDataTableProxy(
|
||||
workflow,
|
||||
node,
|
||||
'dataStore-id',
|
||||
);
|
||||
await dataStoreOperations.getManyRowsAndCount(options);
|
||||
|
||||
expect(dataStoreServiceMock.getManyRowsAndCount).toBeCalledWith(
|
||||
expect(dataTableServiceMock.getManyRowsAndCount).toBeCalledWith(
|
||||
'dataStore-id',
|
||||
PROJECT_ID,
|
||||
options,
|
||||
|
|
@ -202,14 +202,14 @@ describe('DataStoreProxyService', () => {
|
|||
it('should call insertRows with correct parameters', async () => {
|
||||
const rows = [{ id: 1, name: 'row1' }];
|
||||
|
||||
const dataStoreOperations = await dataStoreProxyService.getDataStoreProxy(
|
||||
const dataStoreOperations = await dataStoreProxyService.getDataTableProxy(
|
||||
workflow,
|
||||
node,
|
||||
'dataStore-id',
|
||||
);
|
||||
await dataStoreOperations.insertRows(rows, 'count');
|
||||
|
||||
expect(dataStoreServiceMock.insertRows).toBeCalledWith(
|
||||
expect(dataTableServiceMock.insertRows).toBeCalledWith(
|
||||
'dataStore-id',
|
||||
PROJECT_ID,
|
||||
rows,
|
||||
|
|
@ -218,7 +218,7 @@ describe('DataStoreProxyService', () => {
|
|||
});
|
||||
|
||||
it('should call upsertRow with correct parameters', async () => {
|
||||
const options: UpsertDataStoreRowOptions = {
|
||||
const options: UpsertDataTableRowOptions = {
|
||||
filter: {
|
||||
filters: [{ columnName: 'name', condition: 'eq', value: 'test' }],
|
||||
type: 'and',
|
||||
|
|
@ -226,14 +226,14 @@ describe('DataStoreProxyService', () => {
|
|||
data: { name: 'newName' },
|
||||
};
|
||||
|
||||
const dataStoreOperations = await dataStoreProxyService.getDataStoreProxy(
|
||||
const dataStoreOperations = await dataStoreProxyService.getDataTableProxy(
|
||||
workflow,
|
||||
node,
|
||||
'dataStore-id',
|
||||
);
|
||||
await dataStoreOperations.upsertRow(options);
|
||||
|
||||
expect(dataStoreServiceMock.upsertRow).toBeCalledWith(
|
||||
expect(dataTableServiceMock.upsertRow).toBeCalledWith(
|
||||
'dataStore-id',
|
||||
PROJECT_ID,
|
||||
options,
|
||||
|
|
@ -243,7 +243,7 @@ describe('DataStoreProxyService', () => {
|
|||
});
|
||||
|
||||
it('should call upsertRow dry run with correct parameters', async () => {
|
||||
const options: UpsertDataStoreRowOptions = {
|
||||
const options: UpsertDataTableRowOptions = {
|
||||
filter: {
|
||||
filters: [{ columnName: 'name', condition: 'eq', value: 'test' }],
|
||||
type: 'and',
|
||||
|
|
@ -252,14 +252,14 @@ describe('DataStoreProxyService', () => {
|
|||
dryRun: true,
|
||||
};
|
||||
|
||||
const dataStoreOperations = await dataStoreProxyService.getDataStoreProxy(
|
||||
const dataStoreOperations = await dataStoreProxyService.getDataTableProxy(
|
||||
workflow,
|
||||
node,
|
||||
'dataStore-id',
|
||||
);
|
||||
await dataStoreOperations.upsertRow(options);
|
||||
|
||||
expect(dataStoreServiceMock.upsertRow).toBeCalledWith(
|
||||
expect(dataTableServiceMock.upsertRow).toBeCalledWith(
|
||||
'dataStore-id',
|
||||
PROJECT_ID,
|
||||
options,
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
import { mockInstance } from '@n8n/backend-test-utils';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
|
||||
import { DataStoreSizeValidator } from '../data-store-size-validator.service';
|
||||
import { DataTableSizeValidator } from '../data-table-size-validator.service';
|
||||
|
||||
describe('DataStoreSizeValidator', () => {
|
||||
let validator: DataStoreSizeValidator;
|
||||
describe('DataTableSizeValidator', () => {
|
||||
let validator: DataTableSizeValidator;
|
||||
let fetchSizeFn: jest.Mock;
|
||||
const globalConfig = mockInstance(GlobalConfig, {
|
||||
dataTable: {
|
||||
|
|
@ -14,7 +14,7 @@ describe('DataStoreSizeValidator', () => {
|
|||
},
|
||||
});
|
||||
beforeEach(() => {
|
||||
validator = new DataStoreSizeValidator(globalConfig);
|
||||
validator = new DataTableSizeValidator(globalConfig);
|
||||
fetchSizeFn = jest.fn();
|
||||
});
|
||||
|
||||
|
|
@ -40,7 +40,7 @@ describe('DataStoreSizeValidator', () => {
|
|||
|
||||
await expect(
|
||||
validator.validateSize(fetchSizeFn, new Date('2024-01-01T00:00:00Z')),
|
||||
).rejects.toThrow('Data store size limit exceeded: 150MB used, limit is 100MB');
|
||||
).rejects.toThrow('Data table size limit exceeded: 150MB used, limit is 100MB');
|
||||
});
|
||||
|
||||
it('should throw error when size equals limit', async () => {
|
||||
|
|
@ -48,7 +48,7 @@ describe('DataStoreSizeValidator', () => {
|
|||
|
||||
await expect(
|
||||
validator.validateSize(fetchSizeFn, new Date('2024-01-01T00:00:00Z')),
|
||||
).rejects.toThrow('Data store size limit exceeded: 100MB used, limit is 100MB');
|
||||
).rejects.toThrow('Data table size limit exceeded: 100MB used, limit is 100MB');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -95,13 +95,13 @@ describe('DataStoreSizeValidator', () => {
|
|||
const time1 = new Date('2024-01-01T00:00:00Z');
|
||||
|
||||
await expect(validator.validateSize(fetchSizeFn, time1)).rejects.toThrow(
|
||||
'Data store size limit exceeded: 100MB used, limit is 100MB',
|
||||
'Data table size limit exceeded: 100MB used, limit is 100MB',
|
||||
);
|
||||
|
||||
// Subsequent calls within cache duration should also fail
|
||||
const time2 = new Date('2024-01-01T00:00:00.500Z');
|
||||
await expect(validator.validateSize(fetchSizeFn, time2)).rejects.toThrow(
|
||||
'Data store size limit exceeded: 100MB used, limit is 100MB',
|
||||
'Data table size limit exceeded: 100MB used, limit is 100MB',
|
||||
);
|
||||
|
||||
// Size was only fetched once
|
||||
|
|
@ -161,13 +161,13 @@ describe('DataStoreSizeValidator', () => {
|
|||
|
||||
// All should fail with the same error
|
||||
await expect(promise1).rejects.toThrow(
|
||||
'Data store size limit exceeded: 150MB used, limit is 100MB',
|
||||
'Data table size limit exceeded: 150MB used, limit is 100MB',
|
||||
);
|
||||
await expect(promise2).rejects.toThrow(
|
||||
'Data store size limit exceeded: 150MB used, limit is 100MB',
|
||||
'Data table size limit exceeded: 150MB used, limit is 100MB',
|
||||
);
|
||||
await expect(promise3).rejects.toThrow(
|
||||
'Data store size limit exceeded: 150MB used, limit is 100MB',
|
||||
'Data table size limit exceeded: 150MB used, limit is 100MB',
|
||||
);
|
||||
|
||||
// Should only fetch once
|
||||
|
|
@ -219,13 +219,13 @@ describe('DataStoreSizeValidator', () => {
|
|||
fetchSizeFn.mockResolvedValueOnce({ totalBytes: 100 * 1024 * 1024, dataTables: {} });
|
||||
const time3 = new Date('2024-01-01T00:00:01.001Z');
|
||||
await expect(validator.validateSize(fetchSizeFn, time3)).rejects.toThrow(
|
||||
'Data store size limit exceeded: 100MB used, limit is 100MB',
|
||||
'Data table size limit exceeded: 100MB used, limit is 100MB',
|
||||
);
|
||||
|
||||
// Subsequent calls use the cached "full" state and continue to fail correctly
|
||||
const time4 = new Date('2024-01-01T00:00:01.500Z');
|
||||
await expect(validator.validateSize(fetchSizeFn, time4)).rejects.toThrow(
|
||||
'Data store size limit exceeded: 100MB used, limit is 100MB',
|
||||
'Data table size limit exceeded: 100MB used, limit is 100MB',
|
||||
);
|
||||
|
||||
expect(fetchSizeFn).toHaveBeenCalledTimes(2);
|
||||
|
|
|
|||
|
|
@ -5,10 +5,10 @@ import { Container } from '@n8n/di';
|
|||
|
||||
import { createUser } from '@test-integration/db/users';
|
||||
|
||||
import { DataStoreSizeValidator } from '../data-store-size-validator.service';
|
||||
import { DataStoreRepository } from '../data-store.repository';
|
||||
import { DataStoreService } from '../data-store.service';
|
||||
import { DataStoreValidationError } from '../errors/data-store-validation.error';
|
||||
import { DataTableSizeValidator } from '../data-table-size-validator.service';
|
||||
import { DataTableRepository } from '../data-table.repository';
|
||||
import { DataTableService } from '../data-table.service';
|
||||
import { DataTableValidationError } from '../errors/data-table-validation.error';
|
||||
|
||||
beforeAll(async () => {
|
||||
await testModules.loadModules(['data-table']);
|
||||
|
|
@ -16,11 +16,11 @@ beforeAll(async () => {
|
|||
});
|
||||
|
||||
beforeEach(async () => {
|
||||
const dataStoreService = Container.get(DataStoreService);
|
||||
await dataStoreService.deleteDataStoreAll();
|
||||
const dataStoreService = Container.get(DataTableService);
|
||||
await dataStoreService.deleteDataTableAll();
|
||||
await testDb.truncate(['DataTable', 'DataTableColumn']);
|
||||
|
||||
const dataStoreSizeValidator = Container.get(DataStoreSizeValidator);
|
||||
const dataStoreSizeValidator = Container.get(DataTableSizeValidator);
|
||||
dataStoreSizeValidator.reset();
|
||||
});
|
||||
|
||||
|
|
@ -28,13 +28,13 @@ afterAll(async () => {
|
|||
await testDb.terminate();
|
||||
});
|
||||
|
||||
describe('Data Store Size Tests', () => {
|
||||
let dataStoreService: DataStoreService;
|
||||
let dataStoreRepository: DataStoreRepository;
|
||||
describe('Data Table Size Tests', () => {
|
||||
let dataStoreService: DataTableService;
|
||||
let dataStoreRepository: DataTableRepository;
|
||||
|
||||
beforeAll(() => {
|
||||
dataStoreService = Container.get(DataStoreService);
|
||||
dataStoreRepository = Container.get(DataStoreRepository);
|
||||
dataStoreService = Container.get(DataTableService);
|
||||
dataStoreRepository = Container.get(DataTableRepository);
|
||||
});
|
||||
|
||||
let project1: Project;
|
||||
|
|
@ -46,12 +46,12 @@ describe('Data Store Size Tests', () => {
|
|||
describe('size validation', () => {
|
||||
it('should prevent insertRows when size limit exceeded', async () => {
|
||||
// ARRANGE
|
||||
const dataStoreSizeValidator = Container.get(DataStoreSizeValidator);
|
||||
const dataStoreSizeValidator = Container.get(DataTableSizeValidator);
|
||||
dataStoreSizeValidator.reset();
|
||||
|
||||
const maxSize = Container.get(GlobalConfig).dataTable.maxSize;
|
||||
|
||||
const { id: dataStoreId } = await dataStoreService.createDataStore(project1.id, {
|
||||
const { id: dataStoreId } = await dataStoreService.createDataTable(project1.id, {
|
||||
name: 'dataStore',
|
||||
columns: [{ name: 'data', type: 'string' }],
|
||||
});
|
||||
|
|
@ -63,7 +63,7 @@ describe('Data Store Size Tests', () => {
|
|||
// ACT & ASSERT
|
||||
await expect(
|
||||
dataStoreService.insertRows(dataStoreId, project1.id, [{ data: 'test' }]),
|
||||
).rejects.toThrow(DataStoreValidationError);
|
||||
).rejects.toThrow(DataTableValidationError);
|
||||
|
||||
expect(mockFindDataTablesSize).toHaveBeenCalled();
|
||||
mockFindDataTablesSize.mockRestore();
|
||||
|
|
@ -71,12 +71,12 @@ describe('Data Store Size Tests', () => {
|
|||
|
||||
it('should prevent updateRows when size limit exceeded', async () => {
|
||||
// ARRANGE
|
||||
const dataStoreSizeValidator = Container.get(DataStoreSizeValidator);
|
||||
const dataStoreSizeValidator = Container.get(DataTableSizeValidator);
|
||||
dataStoreSizeValidator.reset();
|
||||
|
||||
const maxSize = Container.get(GlobalConfig).dataTable.maxSize;
|
||||
|
||||
const { id: dataStoreId } = await dataStoreService.createDataStore(project1.id, {
|
||||
const { id: dataStoreId } = await dataStoreService.createDataTable(project1.id, {
|
||||
name: 'dataStore',
|
||||
columns: [{ name: 'data', type: 'string' }],
|
||||
});
|
||||
|
|
@ -95,7 +95,7 @@ describe('Data Store Size Tests', () => {
|
|||
},
|
||||
data: { data: 'updated' },
|
||||
}),
|
||||
).rejects.toThrow(DataStoreValidationError);
|
||||
).rejects.toThrow(DataTableValidationError);
|
||||
|
||||
expect(mockFindDataTablesSize).toHaveBeenCalled();
|
||||
mockFindDataTablesSize.mockRestore();
|
||||
|
|
@ -106,7 +106,7 @@ describe('Data Store Size Tests', () => {
|
|||
|
||||
const maxSize = Container.get(GlobalConfig).dataTable.maxSize;
|
||||
|
||||
const { id: dataStoreId } = await dataStoreService.createDataStore(project1.id, {
|
||||
const { id: dataStoreId } = await dataStoreService.createDataTable(project1.id, {
|
||||
name: 'dataStore',
|
||||
columns: [{ name: 'data', type: 'string' }],
|
||||
});
|
||||
|
|
@ -124,7 +124,7 @@ describe('Data Store Size Tests', () => {
|
|||
},
|
||||
data: { data: 'new' },
|
||||
}),
|
||||
).rejects.toThrow(DataStoreValidationError);
|
||||
).rejects.toThrow(DataTableValidationError);
|
||||
|
||||
expect(mockFindDataTablesSize).toHaveBeenCalled();
|
||||
mockFindDataTablesSize.mockRestore();
|
||||
|
|
@ -150,12 +150,12 @@ describe('Data Store Size Tests', () => {
|
|||
|
||||
it('should return all data tables for admin user', async () => {
|
||||
// ARRANGE
|
||||
const dataStore1 = await dataStoreService.createDataStore(project1.id, {
|
||||
const dataStore1 = await dataStoreService.createDataTable(project1.id, {
|
||||
name: 'project1-dataStore',
|
||||
columns: [{ name: 'data', type: 'string' }],
|
||||
});
|
||||
|
||||
const dataStore2 = await dataStoreService.createDataStore(project2.id, {
|
||||
const dataStore2 = await dataStoreService.createDataTable(project2.id, {
|
||||
name: 'project2-dataStore',
|
||||
columns: [{ name: 'data', type: 'string' }],
|
||||
});
|
||||
|
|
@ -201,12 +201,12 @@ describe('Data Store Size Tests', () => {
|
|||
// ARRANGE
|
||||
await linkUserToProject(regularUser, project1, 'project:viewer');
|
||||
|
||||
const dataStore1 = await dataStoreService.createDataStore(project1.id, {
|
||||
const dataStore1 = await dataStoreService.createDataTable(project1.id, {
|
||||
name: 'accessible-dataStore',
|
||||
columns: [{ name: 'data', type: 'string' }],
|
||||
});
|
||||
|
||||
const dataStore2 = await dataStoreService.createDataStore(project2.id, {
|
||||
const dataStore2 = await dataStoreService.createDataTable(project2.id, {
|
||||
name: 'inaccessible-dataStore',
|
||||
columns: [{ name: 'data', type: 'string' }],
|
||||
});
|
||||
|
|
@ -233,12 +233,12 @@ describe('Data Store Size Tests', () => {
|
|||
|
||||
it('should return empty dataTables but full totalBytes when user has no project access', async () => {
|
||||
// ARRANGE
|
||||
const dataStore1 = await dataStoreService.createDataStore(project1.id, {
|
||||
const dataStore1 = await dataStoreService.createDataTable(project1.id, {
|
||||
name: 'inaccessible-dataStore1',
|
||||
columns: [{ name: 'data', type: 'string' }],
|
||||
});
|
||||
|
||||
const dataStore2 = await dataStoreService.createDataStore(project2.id, {
|
||||
const dataStore2 = await dataStoreService.createDataTable(project2.id, {
|
||||
name: 'inaccessible-dataStore2',
|
||||
columns: [{ name: 'data', type: 'string' }],
|
||||
});
|
||||
|
|
@ -271,8 +271,8 @@ describe('Data Store Size Tests', () => {
|
|||
|
||||
it('should handle data tables with no rows', async () => {
|
||||
// ARRANGE
|
||||
const dataStore = await dataStoreService.createDataStore(project1.id, {
|
||||
name: 'emptyDataStore',
|
||||
const dataStore = await dataStoreService.createDataTable(project1.id, {
|
||||
name: 'emptyDataTable',
|
||||
columns: [{ name: 'data', type: 'string' }],
|
||||
});
|
||||
|
||||
|
|
@ -285,7 +285,7 @@ describe('Data Store Size Tests', () => {
|
|||
expect(result.dataTables).toBeDefined();
|
||||
expect(result.dataTables[dataStore.id]).toBeDefined();
|
||||
expect(result.dataTables[dataStore.id].sizeBytes).toBeGreaterThanOrEqual(0);
|
||||
expect(result.dataTables[dataStore.id].name).toBe('emptyDataStore');
|
||||
expect(result.dataTables[dataStore.id].name).toBe('emptyDataTable');
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -303,7 +303,7 @@ describe('Data Store Size Tests', () => {
|
|||
|
||||
it('should cache data globally and filter based on user permissions', async () => {
|
||||
// ARRANGE
|
||||
const dataStore = await dataStoreService.createDataStore(project1.id, {
|
||||
const dataStore = await dataStoreService.createDataTable(project1.id, {
|
||||
name: 'test-dataStore',
|
||||
columns: [{ name: 'data', type: 'string' }],
|
||||
});
|
||||
|
|
@ -332,8 +332,8 @@ describe('Data Store Size Tests', () => {
|
|||
|
||||
it('should use global cache for both data fetching and size validation', async () => {
|
||||
// ARRANGE
|
||||
const dataStoreSizeValidator = Container.get(DataStoreSizeValidator);
|
||||
const dataStore = await dataStoreService.createDataStore(project1.id, {
|
||||
const dataStoreSizeValidator = Container.get(DataTableSizeValidator);
|
||||
const dataStore = await dataStoreService.createDataTable(project1.id, {
|
||||
name: 'test-dataStore',
|
||||
columns: [{ name: 'data', type: 'string' }],
|
||||
});
|
||||
|
|
@ -366,8 +366,8 @@ describe('Data Store Size Tests', () => {
|
|||
|
||||
it('should invalidate cache on data modifications', async () => {
|
||||
// ARRANGE
|
||||
const dataStoreSizeValidator = Container.get(DataStoreSizeValidator);
|
||||
const dataStore = await dataStoreService.createDataStore(project1.id, {
|
||||
const dataStoreSizeValidator = Container.get(DataTableSizeValidator);
|
||||
const dataStore = await dataStoreService.createDataTable(project1.id, {
|
||||
name: 'test-dataStore',
|
||||
columns: [{ name: 'data', type: 'string' }],
|
||||
});
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,9 +1,9 @@
|
|||
import { Container } from '@n8n/di';
|
||||
|
||||
import { DataStoreSizeValidator } from '../data-store-size-validator.service';
|
||||
import { DataTableSizeValidator } from '../data-table-size-validator.service';
|
||||
|
||||
export function mockDataStoreSizeValidator() {
|
||||
const sizeValidator = Container.get(DataStoreSizeValidator);
|
||||
export function mockDataTableSizeValidator() {
|
||||
const sizeValidator = Container.get(DataTableSizeValidator);
|
||||
jest.spyOn(sizeValidator, 'validateSize').mockResolvedValue();
|
||||
jest.spyOn(sizeValidator, 'getCachedSizeData').mockResolvedValue({
|
||||
totalBytes: 50 * 1024 * 1024, // 50MB - under the default limit
|
||||
|
|
|
|||
|
|
@ -1,23 +1,23 @@
|
|||
import { ListDataStoreQueryDto } from '@n8n/api-types';
|
||||
import { ListDataTableQueryDto } from '@n8n/api-types';
|
||||
import { AuthenticatedRequest } from '@n8n/db';
|
||||
import { Get, GlobalScope, Query, RestController } from '@n8n/decorators';
|
||||
|
||||
import { DataStoreAggregateService } from './data-store-aggregate.service';
|
||||
import { DataStoreService } from './data-store.service';
|
||||
import { DataTableAggregateService } from './data-table-aggregate.service';
|
||||
import { DataTableService } from './data-table.service';
|
||||
|
||||
@RestController('/data-tables-global')
|
||||
export class DataStoreAggregateController {
|
||||
export class DataTableAggregateController {
|
||||
constructor(
|
||||
private readonly dataStoreAggregateService: DataStoreAggregateService,
|
||||
private readonly dataStoreService: DataStoreService,
|
||||
private readonly dataStoreAggregateService: DataTableAggregateService,
|
||||
private readonly dataStoreService: DataTableService,
|
||||
) {}
|
||||
|
||||
@Get('/')
|
||||
@GlobalScope('dataStore:list')
|
||||
async listDataStores(
|
||||
async listDataTables(
|
||||
req: AuthenticatedRequest,
|
||||
_res: Response,
|
||||
@Query payload: ListDataStoreQueryDto,
|
||||
@Query payload: ListDataTableQueryDto,
|
||||
) {
|
||||
return await this.dataStoreAggregateService.getManyAndCount(req.user, payload);
|
||||
}
|
||||
|
|
@ -1,16 +1,16 @@
|
|||
import type { ListDataStoreQueryDto } from '@n8n/api-types';
|
||||
import type { ListDataTableQueryDto } from '@n8n/api-types';
|
||||
import { Logger } from '@n8n/backend-common';
|
||||
import { User } from '@n8n/db';
|
||||
import { Service } from '@n8n/di';
|
||||
|
||||
import { ProjectService } from '@/services/project.service.ee';
|
||||
|
||||
import { DataStoreRepository } from './data-store.repository';
|
||||
import { DataTableRepository } from './data-table.repository';
|
||||
|
||||
@Service()
|
||||
export class DataStoreAggregateService {
|
||||
export class DataTableAggregateService {
|
||||
constructor(
|
||||
private readonly dataStoreRepository: DataStoreRepository,
|
||||
private readonly dataTableRepository: DataTableRepository,
|
||||
private readonly projectService: ProjectService,
|
||||
private readonly logger: Logger,
|
||||
) {
|
||||
|
|
@ -19,7 +19,7 @@ export class DataStoreAggregateService {
|
|||
async start() {}
|
||||
async shutdown() {}
|
||||
|
||||
async getManyAndCount(user: User, options: ListDataStoreQueryDto) {
|
||||
async getManyAndCount(user: User, options: ListDataTableQueryDto) {
|
||||
const projects = await this.projectService.getProjectRelationsForUser(user);
|
||||
let projectIds = projects.map((x) => x.projectId);
|
||||
if (options.filter?.projectId) {
|
||||
|
|
@ -31,7 +31,7 @@ export class DataStoreAggregateService {
|
|||
return { count: 0, data: [] };
|
||||
}
|
||||
|
||||
return await this.dataStoreRepository.getManyAndCount({
|
||||
return await this.dataTableRepository.getManyAndCount({
|
||||
...options,
|
||||
filter: {
|
||||
...options.filter,
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { DataStoreCreateColumnSchema } from '@n8n/api-types';
|
||||
import { DataTableCreateColumnSchema } from '@n8n/api-types';
|
||||
import { withTransaction } from '@n8n/db';
|
||||
import { Service } from '@n8n/di';
|
||||
import { DataSource, EntityManager, Repository } from '@n8n/typeorm';
|
||||
|
|
@ -8,18 +8,18 @@ import {
|
|||
UnexpectedError,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { DataStoreRowsRepository } from './data-store-rows.repository';
|
||||
import { DataTableColumn } from './data-table-column.entity';
|
||||
import { DataTableRowsRepository } from './data-table-rows.repository';
|
||||
import { DataTable } from './data-table.entity';
|
||||
import { DataStoreColumnNameConflictError } from './errors/data-store-column-name-conflict.error';
|
||||
import { DataStoreSystemColumnNameConflictError } from './errors/data-store-system-column-name-conflict.error';
|
||||
import { DataStoreValidationError } from './errors/data-store-validation.error';
|
||||
import { DataTableColumnNameConflictError } from './errors/data-table-column-name-conflict.error';
|
||||
import { DataTableSystemColumnNameConflictError } from './errors/data-table-system-column-name-conflict.error';
|
||||
import { DataTableValidationError } from './errors/data-table-validation.error';
|
||||
|
||||
@Service()
|
||||
export class DataStoreColumnRepository extends Repository<DataTableColumn> {
|
||||
export class DataTableColumnRepository extends Repository<DataTableColumn> {
|
||||
constructor(
|
||||
dataSource: DataSource,
|
||||
private dataStoreRowsRepository: DataStoreRowsRepository,
|
||||
private dataTableRowsRepository: DataTableRowsRepository,
|
||||
) {
|
||||
super(DataTableColumn, dataSource.manager);
|
||||
}
|
||||
|
|
@ -44,13 +44,13 @@ export class DataStoreColumnRepository extends Repository<DataTableColumn> {
|
|||
);
|
||||
}
|
||||
|
||||
async addColumn(dataTableId: string, schema: DataStoreCreateColumnSchema, trx?: EntityManager) {
|
||||
async addColumn(dataTableId: string, schema: DataTableCreateColumnSchema, trx?: EntityManager) {
|
||||
return await withTransaction(this.manager, trx, async (em) => {
|
||||
if (DATA_TABLE_SYSTEM_COLUMNS.includes(schema.name)) {
|
||||
throw new DataStoreSystemColumnNameConflictError(schema.name);
|
||||
throw new DataTableSystemColumnNameConflictError(schema.name);
|
||||
}
|
||||
if (schema.name === DATA_TABLE_SYSTEM_TESTING_COLUMN) {
|
||||
throw new DataStoreSystemColumnNameConflictError(schema.name, 'testing');
|
||||
throw new DataTableSystemColumnNameConflictError(schema.name, 'testing');
|
||||
}
|
||||
|
||||
const existingColumnMatch = await em.existsBy(DataTableColumn, {
|
||||
|
|
@ -61,9 +61,9 @@ export class DataStoreColumnRepository extends Repository<DataTableColumn> {
|
|||
if (existingColumnMatch) {
|
||||
const dataTable = await em.findOneBy(DataTable, { id: dataTableId });
|
||||
if (!dataTable) {
|
||||
throw new UnexpectedError('Data store not found');
|
||||
throw new UnexpectedError('Data table not found');
|
||||
}
|
||||
throw new DataStoreColumnNameConflictError(schema.name, dataTable.name);
|
||||
throw new DataTableColumnNameConflictError(schema.name, dataTable.name);
|
||||
}
|
||||
|
||||
if (schema.index === undefined) {
|
||||
|
|
@ -81,7 +81,7 @@ export class DataStoreColumnRepository extends Repository<DataTableColumn> {
|
|||
// @ts-ignore Workaround for intermittent typecheck issue with _QueryDeepPartialEntity
|
||||
await em.insert(DataTableColumn, column);
|
||||
|
||||
await this.dataStoreRowsRepository.addColumn(
|
||||
await this.dataTableRowsRepository.addColumn(
|
||||
dataTableId,
|
||||
column,
|
||||
em.connection.options.type,
|
||||
|
|
@ -92,17 +92,17 @@ export class DataStoreColumnRepository extends Repository<DataTableColumn> {
|
|||
});
|
||||
}
|
||||
|
||||
async deleteColumn(dataStoreId: string, column: DataTableColumn, trx?: EntityManager) {
|
||||
async deleteColumn(dataTableId: string, column: DataTableColumn, trx?: EntityManager) {
|
||||
await withTransaction(this.manager, trx, async (em) => {
|
||||
await em.remove(DataTableColumn, column);
|
||||
|
||||
await this.dataStoreRowsRepository.dropColumnFromTable(
|
||||
dataStoreId,
|
||||
await this.dataTableRowsRepository.dropColumnFromTable(
|
||||
dataTableId,
|
||||
column.name,
|
||||
em.connection.options.type,
|
||||
em,
|
||||
);
|
||||
await this.shiftColumns(dataStoreId, column.index, -1, em);
|
||||
await this.shiftColumns(dataTableId, column.index, -1, em);
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -116,11 +116,11 @@ export class DataStoreColumnRepository extends Repository<DataTableColumn> {
|
|||
const columnCount = await em.countBy(DataTableColumn, { dataTableId });
|
||||
|
||||
if (targetIndex < 0) {
|
||||
throw new DataStoreValidationError('tried to move column to negative index');
|
||||
throw new DataTableValidationError('tried to move column to negative index');
|
||||
}
|
||||
|
||||
if (targetIndex >= columnCount) {
|
||||
throw new DataStoreValidationError(
|
||||
throw new DataTableValidationError(
|
||||
'tried to move column to an index larger than column count',
|
||||
);
|
||||
}
|
||||
|
|
@ -1,30 +1,30 @@
|
|||
import type { DataStoreListOptions } from '@n8n/api-types';
|
||||
import type { DataTableListOptions } from '@n8n/api-types';
|
||||
import { Logger } from '@n8n/backend-common';
|
||||
import { Service } from '@n8n/di';
|
||||
import {
|
||||
AddDataStoreColumnOptions,
|
||||
CreateDataStoreOptions,
|
||||
DataStore,
|
||||
DataStoreColumn,
|
||||
DataStoreProxyProvider,
|
||||
DataStoreRows,
|
||||
AddDataTableColumnOptions,
|
||||
CreateDataTableOptions,
|
||||
DataTable,
|
||||
DataTableColumn,
|
||||
DataTableProxyProvider,
|
||||
DataTableRows,
|
||||
DeleteDataTableRowsOptions,
|
||||
IDataStoreProjectAggregateService,
|
||||
IDataStoreProjectService,
|
||||
IDataTableProjectAggregateService,
|
||||
IDataTableProjectService,
|
||||
DataTableInsertRowsReturnType,
|
||||
INode,
|
||||
ListDataStoreOptions,
|
||||
ListDataStoreRowsOptions,
|
||||
MoveDataStoreColumnOptions,
|
||||
UpdateDataStoreOptions,
|
||||
UpdateDataStoreRowOptions,
|
||||
UpsertDataStoreRowOptions,
|
||||
ListDataTableOptions,
|
||||
ListDataTableRowsOptions,
|
||||
MoveDataTableColumnOptions,
|
||||
UpdateDataTableOptions,
|
||||
UpdateDataTableRowOptions,
|
||||
UpsertDataTableRowOptions,
|
||||
Workflow,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { OwnershipService } from '@/services/ownership.service';
|
||||
|
||||
import { DataStoreService } from './data-store.service';
|
||||
import { DataTableService } from './data-table.service';
|
||||
|
||||
const ALLOWED_NODES = [
|
||||
'n8n-nodes-base.dataTable',
|
||||
|
|
@ -40,9 +40,9 @@ export function isAllowedNode(s: string): s is AllowedNode {
|
|||
}
|
||||
|
||||
@Service()
|
||||
export class DataStoreProxyService implements DataStoreProxyProvider {
|
||||
export class DataTableProxyService implements DataTableProxyProvider {
|
||||
constructor(
|
||||
private readonly dataStoreService: DataStoreService,
|
||||
private readonly dataStoreService: DataTableService,
|
||||
private readonly ownershipService: OwnershipService,
|
||||
private readonly logger: Logger,
|
||||
) {
|
||||
|
|
@ -60,80 +60,80 @@ export class DataStoreProxyService implements DataStoreProxyProvider {
|
|||
return homeProject.id;
|
||||
}
|
||||
|
||||
async getDataStoreAggregateProxy(
|
||||
async getDataTableAggregateProxy(
|
||||
workflow: Workflow,
|
||||
node: INode,
|
||||
projectId?: string,
|
||||
): Promise<IDataStoreProjectAggregateService> {
|
||||
): Promise<IDataTableProjectAggregateService> {
|
||||
this.validateRequest(node);
|
||||
projectId = projectId ?? (await this.getProjectId(workflow));
|
||||
|
||||
return this.makeAggregateOperations(projectId);
|
||||
}
|
||||
|
||||
async getDataStoreProxy(
|
||||
async getDataTableProxy(
|
||||
workflow: Workflow,
|
||||
node: INode,
|
||||
dataStoreId: string,
|
||||
projectId?: string,
|
||||
): Promise<IDataStoreProjectService> {
|
||||
): Promise<IDataTableProjectService> {
|
||||
this.validateRequest(node);
|
||||
projectId = projectId ?? (await this.getProjectId(workflow));
|
||||
|
||||
return this.makeDataStoreOperations(projectId, dataStoreId);
|
||||
return this.makeDataTableOperations(projectId, dataStoreId);
|
||||
}
|
||||
|
||||
private makeAggregateOperations(projectId: string): IDataStoreProjectAggregateService {
|
||||
private makeAggregateOperations(projectId: string): IDataTableProjectAggregateService {
|
||||
const dataStoreService = this.dataStoreService;
|
||||
return {
|
||||
getProjectId() {
|
||||
return projectId;
|
||||
},
|
||||
|
||||
async getManyAndCount(options: ListDataStoreOptions = {}) {
|
||||
const serviceOptions: DataStoreListOptions = {
|
||||
async getManyAndCount(options: ListDataTableOptions = {}) {
|
||||
const serviceOptions: DataTableListOptions = {
|
||||
...options,
|
||||
filter: { projectId, ...(options.filter ?? {}) },
|
||||
};
|
||||
return await dataStoreService.getManyAndCount(serviceOptions);
|
||||
},
|
||||
|
||||
async createDataStore(options: CreateDataStoreOptions): Promise<DataStore> {
|
||||
return await dataStoreService.createDataStore(projectId, options);
|
||||
async createDataTable(options: CreateDataTableOptions): Promise<DataTable> {
|
||||
return await dataStoreService.createDataTable(projectId, options);
|
||||
},
|
||||
|
||||
async deleteDataStoreAll(): Promise<boolean> {
|
||||
return await dataStoreService.deleteDataStoreByProjectId(projectId);
|
||||
async deleteDataTableAll(): Promise<boolean> {
|
||||
return await dataStoreService.deleteDataTableByProjectId(projectId);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
private makeDataStoreOperations(
|
||||
private makeDataTableOperations(
|
||||
projectId: string,
|
||||
dataStoreId: string,
|
||||
): Omit<IDataStoreProjectService, keyof IDataStoreProjectAggregateService> {
|
||||
): Omit<IDataTableProjectService, keyof IDataTableProjectAggregateService> {
|
||||
const dataStoreService = this.dataStoreService;
|
||||
|
||||
return {
|
||||
// DataStore management
|
||||
async updateDataStore(options: UpdateDataStoreOptions): Promise<boolean> {
|
||||
return await dataStoreService.updateDataStore(dataStoreId, projectId, options);
|
||||
// DataTable management
|
||||
async updateDataTable(options: UpdateDataTableOptions): Promise<boolean> {
|
||||
return await dataStoreService.updateDataTable(dataStoreId, projectId, options);
|
||||
},
|
||||
|
||||
async deleteDataStore(): Promise<boolean> {
|
||||
return await dataStoreService.deleteDataStore(dataStoreId, projectId);
|
||||
async deleteDataTable(): Promise<boolean> {
|
||||
return await dataStoreService.deleteDataTable(dataStoreId, projectId);
|
||||
},
|
||||
|
||||
// Column operations
|
||||
async getColumns(): Promise<DataStoreColumn[]> {
|
||||
async getColumns(): Promise<DataTableColumn[]> {
|
||||
return await dataStoreService.getColumns(dataStoreId, projectId);
|
||||
},
|
||||
|
||||
async addColumn(options: AddDataStoreColumnOptions): Promise<DataStoreColumn> {
|
||||
async addColumn(options: AddDataTableColumnOptions): Promise<DataTableColumn> {
|
||||
return await dataStoreService.addColumn(dataStoreId, projectId, options);
|
||||
},
|
||||
|
||||
async moveColumn(columnId: string, options: MoveDataStoreColumnOptions): Promise<boolean> {
|
||||
async moveColumn(columnId: string, options: MoveDataTableColumnOptions): Promise<boolean> {
|
||||
return await dataStoreService.moveColumn(dataStoreId, projectId, columnId, options);
|
||||
},
|
||||
|
||||
|
|
@ -142,18 +142,18 @@ export class DataStoreProxyService implements DataStoreProxyProvider {
|
|||
},
|
||||
|
||||
// Row operations
|
||||
async getManyRowsAndCount(options: Partial<ListDataStoreRowsOptions>) {
|
||||
async getManyRowsAndCount(options: Partial<ListDataTableRowsOptions>) {
|
||||
return await dataStoreService.getManyRowsAndCount(dataStoreId, projectId, options);
|
||||
},
|
||||
|
||||
async insertRows<T extends DataTableInsertRowsReturnType>(
|
||||
rows: DataStoreRows,
|
||||
rows: DataTableRows,
|
||||
returnType: T,
|
||||
) {
|
||||
return await dataStoreService.insertRows(dataStoreId, projectId, rows, returnType);
|
||||
},
|
||||
|
||||
async updateRows(options: UpdateDataStoreRowOptions) {
|
||||
async updateRows(options: UpdateDataTableRowOptions) {
|
||||
return await dataStoreService.updateRows(
|
||||
dataStoreId,
|
||||
projectId,
|
||||
|
|
@ -163,7 +163,7 @@ export class DataStoreProxyService implements DataStoreProxyProvider {
|
|||
);
|
||||
},
|
||||
|
||||
async upsertRow(options: UpsertDataStoreRowOptions) {
|
||||
async upsertRow(options: UpsertDataTableRowOptions) {
|
||||
return await dataStoreService.upsertRow(
|
||||
dataStoreId,
|
||||
projectId,
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { ListDataStoreContentQueryDto, DataTableFilter } from '@n8n/api-types';
|
||||
import { DataTableFilter, ListDataTableContentQueryDto } from '@n8n/api-types';
|
||||
import { CreateTable, DslColumn, withTransaction } from '@n8n/db';
|
||||
import { Service } from '@n8n/di';
|
||||
import {
|
||||
|
|
@ -12,18 +12,17 @@ import {
|
|||
DeleteQueryBuilder,
|
||||
} from '@n8n/typeorm';
|
||||
import {
|
||||
DataStoreColumnJsType,
|
||||
DataStoreRows,
|
||||
DataStoreRowReturn,
|
||||
DataTableColumnJsType,
|
||||
DataTableRows,
|
||||
DataTableRowReturn,
|
||||
UnexpectedError,
|
||||
DataStoreRowsReturn,
|
||||
DataTableRowsReturn,
|
||||
DATA_TABLE_SYSTEM_COLUMNS,
|
||||
DataTableInsertRowsReturnType,
|
||||
DataTableInsertRowsResult,
|
||||
DataStoreRowReturnWithState,
|
||||
DataTableRowReturnWithState,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { DataStoreUserTableName } from './data-store.types';
|
||||
import { DataTableColumn } from './data-table-column.entity';
|
||||
import {
|
||||
addColumnQuery,
|
||||
|
|
@ -38,6 +37,7 @@ import {
|
|||
toSqliteGlobFromPercent,
|
||||
toTableName,
|
||||
} from './utils/sql-utils';
|
||||
import { DataTableUserTableName } from './data-table.types';
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
type QueryBuilder = SelectQueryBuilder<any>;
|
||||
|
|
@ -153,12 +153,12 @@ function getConditionAndParams(
|
|||
}
|
||||
|
||||
@Service()
|
||||
export class DataStoreRowsRepository {
|
||||
export class DataTableRowsRepository {
|
||||
constructor(private dataSource: DataSource) {}
|
||||
|
||||
async insertRowsBulk(
|
||||
table: DataStoreUserTableName,
|
||||
rows: DataStoreRows,
|
||||
table: DataTableUserTableName,
|
||||
rows: DataTableRows,
|
||||
columns: DataTableColumn[],
|
||||
trx?: EntityManager,
|
||||
) {
|
||||
|
|
@ -192,9 +192,9 @@ export class DataStoreRowsRepository {
|
|||
|
||||
if (endExclusive <= start) break;
|
||||
|
||||
const completeRows = new Array<DataStoreColumnJsType[]>(endExclusive - start);
|
||||
const completeRows = new Array<DataTableColumnJsType[]>(endExclusive - start);
|
||||
for (let j = start; j < endExclusive; ++j) {
|
||||
const insertArray: DataStoreColumnJsType[] = [];
|
||||
const insertArray: DataTableColumnJsType[] = [];
|
||||
|
||||
for (let h = 0; h < columnNames.length; ++h) {
|
||||
const column = columns[h];
|
||||
|
|
@ -218,25 +218,25 @@ export class DataStoreRowsRepository {
|
|||
}
|
||||
|
||||
async insertRows<T extends DataTableInsertRowsReturnType>(
|
||||
dataStoreId: string,
|
||||
rows: DataStoreRows,
|
||||
dataTableId: string,
|
||||
rows: DataTableRows,
|
||||
columns: DataTableColumn[],
|
||||
returnType: T,
|
||||
trx?: EntityManager,
|
||||
): Promise<DataTableInsertRowsResult<T>>;
|
||||
async insertRows<T extends DataTableInsertRowsReturnType>(
|
||||
dataStoreId: string,
|
||||
rows: DataStoreRows,
|
||||
dataTableId: string,
|
||||
rows: DataTableRows,
|
||||
columns: DataTableColumn[],
|
||||
returnType: T,
|
||||
trx?: EntityManager,
|
||||
): Promise<DataTableInsertRowsResult> {
|
||||
return await withTransaction(this.dataSource.manager, trx, async (em) => {
|
||||
const inserted: Array<Pick<DataStoreRowReturn, 'id'>> = [];
|
||||
const inserted: Array<Pick<DataTableRowReturn, 'id'>> = [];
|
||||
const dbType = this.dataSource.options.type;
|
||||
const useReturning = dbType === 'postgres' || dbType === 'mariadb';
|
||||
|
||||
const table = toTableName(dataStoreId);
|
||||
const table = toTableName(dataTableId);
|
||||
const escapedColumns = columns.map((c) => this.dataSource.driver.escape(c.name));
|
||||
const escapedSystemColumns = DATA_TABLE_SYSTEM_COLUMNS.map((x) =>
|
||||
this.dataSource.driver.escape(x),
|
||||
|
|
@ -289,7 +289,7 @@ export class DataStoreRowsRepository {
|
|||
continue;
|
||||
}
|
||||
|
||||
const insertedRows = await this.getManyByIds(dataStoreId, ids, columns, em);
|
||||
const insertedRows = await this.getManyByIds(dataTableId, ids, columns, em);
|
||||
|
||||
inserted.push(...insertedRows);
|
||||
}
|
||||
|
|
@ -299,16 +299,16 @@ export class DataStoreRowsRepository {
|
|||
}
|
||||
|
||||
async updateRows<T extends boolean | undefined>(
|
||||
dataStoreId: string,
|
||||
data: Record<string, DataStoreColumnJsType | null>,
|
||||
dataTableId: string,
|
||||
data: Record<string, DataTableColumnJsType | null>,
|
||||
filter: DataTableFilter,
|
||||
columns: DataTableColumn[],
|
||||
returnData?: T,
|
||||
trx?: EntityManager,
|
||||
): Promise<T extends true ? DataStoreRowReturn[] : true>;
|
||||
): Promise<T extends true ? DataTableRowReturn[] : true>;
|
||||
async updateRows(
|
||||
dataStoreId: string,
|
||||
data: Record<string, DataStoreColumnJsType | null>,
|
||||
dataTableId: string,
|
||||
data: Record<string, DataTableColumnJsType | null>,
|
||||
filter: DataTableFilter,
|
||||
columns: DataTableColumn[],
|
||||
returnData: boolean = false,
|
||||
|
|
@ -318,7 +318,7 @@ export class DataStoreRowsRepository {
|
|||
const dbType = this.dataSource.options.type;
|
||||
const useReturning = dbType === 'postgres';
|
||||
|
||||
const table = toTableName(dataStoreId);
|
||||
const table = toTableName(dataTableId);
|
||||
const escapedColumns = columns.map((c) => this.dataSource.driver.escape(c.name));
|
||||
const escapedSystemColumns = DATA_TABLE_SYSTEM_COLUMNS.map((x) =>
|
||||
this.dataSource.driver.escape(x),
|
||||
|
|
@ -326,11 +326,11 @@ export class DataStoreRowsRepository {
|
|||
const selectColumns = [...escapedSystemColumns, ...escapedColumns];
|
||||
const setData = this.prepareUpdateData(data, columns, dbType);
|
||||
|
||||
let affectedRows: Array<Pick<DataStoreRowReturn, 'id'>> = [];
|
||||
let affectedRows: Array<Pick<DataTableRowReturn, 'id'>> = [];
|
||||
if (!useReturning && returnData) {
|
||||
// Only Postgres supports RETURNING statement on updates (with our typeorm),
|
||||
// on other engines we must query the list of updates rows later by ID
|
||||
affectedRows = await this.getAffectedRowsForUpdate(dataStoreId, filter, columns, true, trx);
|
||||
affectedRows = await this.getAffectedRowsForUpdate(dataTableId, filter, columns, true, trx);
|
||||
}
|
||||
|
||||
setData.updatedAt = normalizeValue(new Date(), 'date', dbType);
|
||||
|
|
@ -355,21 +355,21 @@ export class DataStoreRowsRepository {
|
|||
}
|
||||
|
||||
const ids = affectedRows.map((row) => row.id);
|
||||
return await this.getManyByIds(dataStoreId, ids, columns, em);
|
||||
return await this.getManyByIds(dataTableId, ids, columns, em);
|
||||
});
|
||||
}
|
||||
|
||||
async dryRunUpdateRows(
|
||||
dataStoreId: string,
|
||||
data: Record<string, DataStoreColumnJsType | null>,
|
||||
dataTableId: string,
|
||||
data: Record<string, DataTableColumnJsType | null>,
|
||||
filter: DataTableFilter,
|
||||
columns: DataTableColumn[],
|
||||
trx?: EntityManager,
|
||||
): Promise<DataStoreRowReturnWithState[]> {
|
||||
): Promise<DataTableRowReturnWithState[]> {
|
||||
const dbType = this.dataSource.options.type;
|
||||
|
||||
const beforeRows = await this.getAffectedRowsForUpdate(
|
||||
dataStoreId,
|
||||
dataTableId,
|
||||
filter,
|
||||
columns,
|
||||
false,
|
||||
|
|
@ -385,13 +385,13 @@ export class DataStoreRowsRepository {
|
|||
}
|
||||
|
||||
async dryRunUpsertRow(
|
||||
dataStoreId: string,
|
||||
data: Record<string, DataStoreColumnJsType | null>,
|
||||
dataTableId: string,
|
||||
data: Record<string, DataTableColumnJsType | null>,
|
||||
filter: DataTableFilter,
|
||||
columns: DataTableColumn[],
|
||||
trx?: EntityManager,
|
||||
): Promise<DataStoreRowReturnWithState[]> {
|
||||
const updateResult = await this.dryRunUpdateRows(dataStoreId, data, filter, columns, trx);
|
||||
): Promise<DataTableRowReturnWithState[]> {
|
||||
const updateResult = await this.dryRunUpdateRows(dataTableId, data, filter, columns, trx);
|
||||
|
||||
if (updateResult.length > 0) {
|
||||
return updateResult;
|
||||
|
|
@ -401,7 +401,7 @@ export class DataStoreRowsRepository {
|
|||
const dbType = this.dataSource.options.type;
|
||||
const now = new Date();
|
||||
const preparedData = this.prepareUpdateData(data, columns, dbType);
|
||||
const insertedRow: DataStoreRowReturn = {
|
||||
const insertedRow: DataTableRowReturn = {
|
||||
id: 0, // Placeholder ID for dry run
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
|
|
@ -446,7 +446,7 @@ export class DataStoreRowsRepository {
|
|||
return true;
|
||||
}
|
||||
|
||||
let affectedRows: DataStoreRowReturn[] = [];
|
||||
let affectedRows: DataTableRowReturn[] = [];
|
||||
|
||||
if (!useReturning) {
|
||||
const selectQuery = em.createQueryBuilder().select('*').from(table, 'dataTable');
|
||||
|
|
@ -455,7 +455,7 @@ export class DataStoreRowsRepository {
|
|||
this.applyFilters(selectQuery, filter, 'dataTable', columns);
|
||||
}
|
||||
|
||||
const rawRows = await selectQuery.getRawMany<DataStoreRowReturn>();
|
||||
const rawRows = await selectQuery.getRawMany<DataTableRowReturn>();
|
||||
affectedRows = normalizeRows(rawRows, columns);
|
||||
}
|
||||
|
||||
|
|
@ -490,18 +490,18 @@ export class DataStoreRowsRepository {
|
|||
}
|
||||
|
||||
private async getAffectedRowsForUpdate<T extends boolean>(
|
||||
dataStoreId: string,
|
||||
dataTableId: string,
|
||||
filter: DataTableFilter,
|
||||
columns: DataTableColumn[],
|
||||
idsOnly: T,
|
||||
trx?: EntityManager,
|
||||
): Promise<T extends true ? Array<Pick<DataStoreRowReturn, 'id'>> : DataStoreRowReturn[]> {
|
||||
): Promise<T extends true ? Array<Pick<DataTableRowReturn, 'id'>> : DataTableRowReturn[]> {
|
||||
return await withTransaction(this.dataSource.manager, trx, async (em) => {
|
||||
const table = toTableName(dataStoreId);
|
||||
const table = toTableName(dataTableId);
|
||||
const selectColumns = idsOnly ? 'id' : '*';
|
||||
const selectQuery = em.createQueryBuilder().select(selectColumns).from(table, 'dataTable');
|
||||
this.applyFilters(selectQuery, filter, 'dataTable', columns);
|
||||
const rawRows: DataStoreRowsReturn = await selectQuery.getRawMany();
|
||||
const rawRows: DataTableRowsReturn = await selectQuery.getRawMany();
|
||||
|
||||
if (idsOnly) {
|
||||
return rawRows;
|
||||
|
|
@ -512,10 +512,10 @@ export class DataStoreRowsRepository {
|
|||
}
|
||||
|
||||
private prepareUpdateData(
|
||||
data: Record<string, DataStoreColumnJsType | null>,
|
||||
data: Record<string, DataTableColumnJsType | null>,
|
||||
columns: DataTableColumn[],
|
||||
dbType: DataSourceOptions['type'],
|
||||
): Record<string, DataStoreColumnJsType | null> {
|
||||
): Record<string, DataTableColumnJsType | null> {
|
||||
const setData = { ...data };
|
||||
for (const column of columns) {
|
||||
if (column.name in setData) {
|
||||
|
|
@ -526,9 +526,9 @@ export class DataStoreRowsRepository {
|
|||
}
|
||||
|
||||
private toDryRunRows(
|
||||
beforeState: DataStoreRowReturn | null,
|
||||
afterState: DataStoreRowReturn | null,
|
||||
): DataStoreRowReturnWithState[] {
|
||||
beforeState: DataTableRowReturn | null,
|
||||
afterState: DataTableRowReturn | null,
|
||||
): DataTableRowReturnWithState[] {
|
||||
if (beforeState === null && afterState === null) {
|
||||
throw new Error('Both before and after rows cannot be null');
|
||||
}
|
||||
|
|
@ -559,7 +559,7 @@ export class DataStoreRowsRepository {
|
|||
}
|
||||
|
||||
async createTableWithColumns(
|
||||
dataStoreId: string,
|
||||
dataTableId: string,
|
||||
columns: DataTableColumn[],
|
||||
trx?: EntityManager,
|
||||
) {
|
||||
|
|
@ -569,7 +569,7 @@ export class DataStoreRowsRepository {
|
|||
}
|
||||
|
||||
const dslColumns = [new DslColumn('id').int.autoGenerate2.primary, ...toDslColumns(columns)];
|
||||
const createTable = new CreateTable(toTableName(dataStoreId), '', em.queryRunner).withColumns(
|
||||
const createTable = new CreateTable(toTableName(dataTableId), '', em.queryRunner).withColumns(
|
||||
...dslColumns,
|
||||
).withTimestamps;
|
||||
|
||||
|
|
@ -577,40 +577,40 @@ export class DataStoreRowsRepository {
|
|||
});
|
||||
}
|
||||
|
||||
async dropTable(dataStoreId: string, trx?: EntityManager) {
|
||||
async dropTable(dataTableId: string, trx?: EntityManager) {
|
||||
await withTransaction(this.dataSource.manager, trx, async (em) => {
|
||||
if (!em.queryRunner) {
|
||||
throw new UnexpectedError('QueryRunner is not available');
|
||||
}
|
||||
await em.queryRunner.dropTable(toTableName(dataStoreId), true);
|
||||
await em.queryRunner.dropTable(toTableName(dataTableId), true);
|
||||
});
|
||||
}
|
||||
|
||||
async addColumn(
|
||||
dataStoreId: string,
|
||||
dataTableId: string,
|
||||
column: DataTableColumn,
|
||||
dbType: DataSourceOptions['type'],
|
||||
trx?: EntityManager,
|
||||
) {
|
||||
await withTransaction(this.dataSource.manager, trx, async (em) => {
|
||||
await em.query(addColumnQuery(toTableName(dataStoreId), column, dbType));
|
||||
await em.query(addColumnQuery(toTableName(dataTableId), column, dbType));
|
||||
});
|
||||
}
|
||||
|
||||
async dropColumnFromTable(
|
||||
dataStoreId: string,
|
||||
dataTableId: string,
|
||||
columnName: string,
|
||||
dbType: DataSourceOptions['type'],
|
||||
trx?: EntityManager,
|
||||
) {
|
||||
await withTransaction(this.dataSource.manager, trx, async (em) => {
|
||||
await em.query(deleteColumnQuery(toTableName(dataStoreId), columnName, dbType));
|
||||
await em.query(deleteColumnQuery(toTableName(dataTableId), columnName, dbType));
|
||||
});
|
||||
}
|
||||
|
||||
async getManyAndCount(
|
||||
dataStoreId: string,
|
||||
dto: ListDataStoreContentQueryDto,
|
||||
dataTableId: string,
|
||||
dto: ListDataTableContentQueryDto,
|
||||
columns?: DataTableColumn[],
|
||||
trx?: EntityManager,
|
||||
) {
|
||||
|
|
@ -618,8 +618,8 @@ export class DataStoreRowsRepository {
|
|||
this.dataSource.manager,
|
||||
trx,
|
||||
async (em) => {
|
||||
const [countQuery, query] = this.getManyQuery(dataStoreId, dto, em, columns);
|
||||
const data: DataStoreRowsReturn = await query.select('*').getRawMany();
|
||||
const [countQuery, query] = this.getManyQuery(dataTableId, dto, em, columns);
|
||||
const data: DataTableRowsReturn = await query.select('*').getRawMany();
|
||||
const countResult = await countQuery.select('COUNT(*) as count').getRawOne<{
|
||||
count: number | string | null;
|
||||
}>();
|
||||
|
|
@ -634,7 +634,7 @@ export class DataStoreRowsRepository {
|
|||
}
|
||||
|
||||
async getManyByIds(
|
||||
dataStoreId: string,
|
||||
dataTableId: string,
|
||||
ids: number[],
|
||||
columns: DataTableColumn[],
|
||||
trx?: EntityManager,
|
||||
|
|
@ -643,7 +643,7 @@ export class DataStoreRowsRepository {
|
|||
this.dataSource.manager,
|
||||
trx,
|
||||
async (em) => {
|
||||
const table = toTableName(dataStoreId);
|
||||
const table = toTableName(dataTableId);
|
||||
const escapedColumns = columns.map((c) => this.dataSource.driver.escape(c.name));
|
||||
const escapedSystemColumns = DATA_TABLE_SYSTEM_COLUMNS.map((x) =>
|
||||
this.dataSource.driver.escape(x),
|
||||
|
|
@ -659,7 +659,7 @@ export class DataStoreRowsRepository {
|
|||
.select(selectColumns)
|
||||
.from(table, 'dataTable')
|
||||
.where({ id: In(ids) })
|
||||
.getRawMany<DataStoreRowReturn>();
|
||||
.getRawMany<DataTableRowReturn>();
|
||||
|
||||
return normalizeRows(rows, columns);
|
||||
},
|
||||
|
|
@ -668,15 +668,15 @@ export class DataStoreRowsRepository {
|
|||
}
|
||||
|
||||
private getManyQuery(
|
||||
dataStoreId: string,
|
||||
dto: ListDataStoreContentQueryDto,
|
||||
dataTableId: string,
|
||||
dto: ListDataTableContentQueryDto,
|
||||
em: EntityManager,
|
||||
columns?: DataTableColumn[],
|
||||
): [QueryBuilder, QueryBuilder] {
|
||||
const query = em.createQueryBuilder();
|
||||
|
||||
const tableReference = 'dataTable';
|
||||
query.from(toTableName(dataStoreId), tableReference);
|
||||
query.from(toTableName(dataTableId), tableReference);
|
||||
if (dto.filter) {
|
||||
this.applyFilters(query, dto.filter, tableReference, columns);
|
||||
}
|
||||
|
|
@ -716,7 +716,7 @@ export class DataStoreRowsRepository {
|
|||
}
|
||||
}
|
||||
|
||||
private applySorting(query: QueryBuilder, dto: ListDataStoreContentQueryDto): void {
|
||||
private applySorting(query: QueryBuilder, dto: ListDataTableContentQueryDto): void {
|
||||
if (!dto.sortBy) {
|
||||
return;
|
||||
}
|
||||
|
|
@ -731,7 +731,7 @@ export class DataStoreRowsRepository {
|
|||
query.orderBy(quotedField, direction);
|
||||
}
|
||||
|
||||
private applyPagination(query: QueryBuilder, dto: ListDataStoreContentQueryDto): void {
|
||||
private applyPagination(query: QueryBuilder, dto: ListDataTableContentQueryDto): void {
|
||||
query.skip(dto.skip ?? 0);
|
||||
if (dto.take) query.take(dto.take);
|
||||
}
|
||||
|
|
@ -2,10 +2,10 @@ import { GlobalConfig } from '@n8n/config';
|
|||
import { Service } from '@n8n/di';
|
||||
import { DataTableSizeStatus, DataTablesSizeData } from 'n8n-workflow';
|
||||
|
||||
import { DataStoreValidationError } from './errors/data-store-validation.error';
|
||||
import { DataTableValidationError } from './errors/data-table-validation.error';
|
||||
|
||||
@Service()
|
||||
export class DataStoreSizeValidator {
|
||||
export class DataTableSizeValidator {
|
||||
private lastCheck: Date | undefined;
|
||||
private cachedSizeData: DataTablesSizeData | undefined;
|
||||
private pendingCheck: Promise<DataTablesSizeData> | null = null;
|
||||
|
|
@ -53,8 +53,8 @@ export class DataStoreSizeValidator {
|
|||
): Promise<void> {
|
||||
const size = await this.getCachedSizeData(fetchSizeFn, now);
|
||||
if (size.totalBytes >= this.globalConfig.dataTable.maxSize) {
|
||||
throw new DataStoreValidationError(
|
||||
`Data store size limit exceeded: ${this.toMb(size.totalBytes)}MB used, limit is ${this.toMb(this.globalConfig.dataTable.maxSize)}MB`,
|
||||
throw new DataTableValidationError(
|
||||
`Data table size limit exceeded: ${this.toMb(size.totalBytes)}MB used, limit is ${this.toMb(this.globalConfig.dataTable.maxSize)}MB`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +1,14 @@
|
|||
import {
|
||||
AddDataStoreRowsDto,
|
||||
AddDataStoreColumnDto,
|
||||
CreateDataStoreDto,
|
||||
AddDataTableRowsDto,
|
||||
AddDataTableColumnDto,
|
||||
CreateDataTableDto,
|
||||
DeleteDataTableRowsDto,
|
||||
ListDataStoreContentQueryDto,
|
||||
ListDataStoreQueryDto,
|
||||
MoveDataStoreColumnDto,
|
||||
UpdateDataStoreDto,
|
||||
ListDataTableContentQueryDto,
|
||||
ListDataTableQueryDto,
|
||||
MoveDataTableColumnDto,
|
||||
UpdateDataTableDto,
|
||||
UpdateDataTableRowDto,
|
||||
UpsertDataStoreRowDto,
|
||||
UpsertDataTableRowDto,
|
||||
} from '@n8n/api-types';
|
||||
import { AuthenticatedRequest } from '@n8n/db';
|
||||
import {
|
||||
|
|
@ -22,38 +22,38 @@ import {
|
|||
Query,
|
||||
RestController,
|
||||
} from '@n8n/decorators';
|
||||
import { DataStoreRowReturn } from 'n8n-workflow';
|
||||
import { DataTableRowReturn } from 'n8n-workflow';
|
||||
|
||||
import { BadRequestError } from '@/errors/response-errors/bad-request.error';
|
||||
import { ConflictError } from '@/errors/response-errors/conflict.error';
|
||||
import { InternalServerError } from '@/errors/response-errors/internal-server.error';
|
||||
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
||||
|
||||
import { DataStoreService } from './data-store.service';
|
||||
import { DataStoreColumnNameConflictError } from './errors/data-store-column-name-conflict.error';
|
||||
import { DataStoreColumnNotFoundError } from './errors/data-store-column-not-found.error';
|
||||
import { DataStoreNameConflictError } from './errors/data-store-name-conflict.error';
|
||||
import { DataStoreNotFoundError } from './errors/data-store-not-found.error';
|
||||
import { DataStoreSystemColumnNameConflictError } from './errors/data-store-system-column-name-conflict.error';
|
||||
import { DataStoreValidationError } from './errors/data-store-validation.error';
|
||||
import { DataTableService } from './data-table.service';
|
||||
import { DataTableColumnNameConflictError } from './errors/data-table-column-name-conflict.error';
|
||||
import { DataTableColumnNotFoundError } from './errors/data-table-column-not-found.error';
|
||||
import { DataTableNameConflictError } from './errors/data-table-name-conflict.error';
|
||||
import { DataTableNotFoundError } from './errors/data-table-not-found.error';
|
||||
import { DataTableSystemColumnNameConflictError } from './errors/data-table-system-column-name-conflict.error';
|
||||
import { DataTableValidationError } from './errors/data-table-validation.error';
|
||||
|
||||
@RestController('/projects/:projectId/data-tables')
|
||||
export class DataStoreController {
|
||||
constructor(private readonly dataStoreService: DataStoreService) {}
|
||||
export class DataTableController {
|
||||
constructor(private readonly dataTableService: DataTableService) {}
|
||||
|
||||
@Post('/')
|
||||
@ProjectScope('dataStore:create')
|
||||
async createDataStore(
|
||||
async createDataTable(
|
||||
req: AuthenticatedRequest<{ projectId: string }>,
|
||||
_res: Response,
|
||||
@Body dto: CreateDataStoreDto,
|
||||
@Body dto: CreateDataTableDto,
|
||||
) {
|
||||
try {
|
||||
return await this.dataStoreService.createDataStore(req.params.projectId, dto);
|
||||
return await this.dataTableService.createDataTable(req.params.projectId, dto);
|
||||
} catch (e: unknown) {
|
||||
if (!(e instanceof Error)) {
|
||||
throw e;
|
||||
} else if (e instanceof DataStoreNameConflictError) {
|
||||
} else if (e instanceof DataTableNameConflictError) {
|
||||
throw new ConflictError(e.message);
|
||||
} else {
|
||||
throw new InternalServerError(e.message, e);
|
||||
|
|
@ -63,32 +63,32 @@ export class DataStoreController {
|
|||
|
||||
@Get('/')
|
||||
@ProjectScope('dataStore:listProject')
|
||||
async listProjectDataStores(
|
||||
async listProjectDataTables(
|
||||
req: AuthenticatedRequest<{ projectId: string }>,
|
||||
_res: Response,
|
||||
@Query payload: ListDataStoreQueryDto,
|
||||
@Query payload: ListDataTableQueryDto,
|
||||
) {
|
||||
const providedFilter = payload?.filter ?? {};
|
||||
return await this.dataStoreService.getManyAndCount({
|
||||
return await this.dataTableService.getManyAndCount({
|
||||
...payload,
|
||||
filter: { ...providedFilter, projectId: req.params.projectId },
|
||||
});
|
||||
}
|
||||
|
||||
@Patch('/:dataStoreId')
|
||||
@Patch('/:dataTableId')
|
||||
@ProjectScope('dataStore:update')
|
||||
async updateDataStore(
|
||||
async updateDataTable(
|
||||
req: AuthenticatedRequest<{ projectId: string }>,
|
||||
_res: Response,
|
||||
@Param('dataStoreId') dataStoreId: string,
|
||||
@Body dto: UpdateDataStoreDto,
|
||||
@Param('dataTableId') dataTableId: string,
|
||||
@Body dto: UpdateDataTableDto,
|
||||
) {
|
||||
try {
|
||||
return await this.dataStoreService.updateDataStore(dataStoreId, req.params.projectId, dto);
|
||||
return await this.dataTableService.updateDataTable(dataTableId, req.params.projectId, dto);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof DataStoreNotFoundError) {
|
||||
if (e instanceof DataTableNotFoundError) {
|
||||
throw new NotFoundError(e.message);
|
||||
} else if (e instanceof DataStoreNameConflictError) {
|
||||
} else if (e instanceof DataTableNameConflictError) {
|
||||
throw new ConflictError(e.message);
|
||||
} else if (e instanceof Error) {
|
||||
throw new InternalServerError(e.message, e);
|
||||
|
|
@ -98,17 +98,17 @@ export class DataStoreController {
|
|||
}
|
||||
}
|
||||
|
||||
@Delete('/:dataStoreId')
|
||||
@Delete('/:dataTableId')
|
||||
@ProjectScope('dataStore:delete')
|
||||
async deleteDataStore(
|
||||
async deleteDataTable(
|
||||
req: AuthenticatedRequest<{ projectId: string }>,
|
||||
_res: Response,
|
||||
@Param('dataStoreId') dataStoreId: string,
|
||||
@Param('dataTableId') dataTableId: string,
|
||||
) {
|
||||
try {
|
||||
return await this.dataStoreService.deleteDataStore(dataStoreId, req.params.projectId);
|
||||
return await this.dataTableService.deleteDataTable(dataTableId, req.params.projectId);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof DataStoreNotFoundError) {
|
||||
if (e instanceof DataTableNotFoundError) {
|
||||
throw new NotFoundError(e.message);
|
||||
} else if (e instanceof Error) {
|
||||
throw new InternalServerError(e.message, e);
|
||||
|
|
@ -118,17 +118,17 @@ export class DataStoreController {
|
|||
}
|
||||
}
|
||||
|
||||
@Get('/:dataStoreId/columns')
|
||||
@Get('/:dataTableId/columns')
|
||||
@ProjectScope('dataStore:read')
|
||||
async getColumns(
|
||||
req: AuthenticatedRequest<{ projectId: string }>,
|
||||
_res: Response,
|
||||
@Param('dataStoreId') dataStoreId: string,
|
||||
@Param('dataTableId') dataTableId: string,
|
||||
) {
|
||||
try {
|
||||
return await this.dataStoreService.getColumns(dataStoreId, req.params.projectId);
|
||||
return await this.dataTableService.getColumns(dataTableId, req.params.projectId);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof DataStoreNotFoundError) {
|
||||
if (e instanceof DataTableNotFoundError) {
|
||||
throw new NotFoundError(e.message);
|
||||
} else if (e instanceof Error) {
|
||||
throw new InternalServerError(e.message, e);
|
||||
|
|
@ -138,22 +138,22 @@ export class DataStoreController {
|
|||
}
|
||||
}
|
||||
|
||||
@Post('/:dataStoreId/columns')
|
||||
@Post('/:dataTableId/columns')
|
||||
@ProjectScope('dataStore:update')
|
||||
async addColumn(
|
||||
req: AuthenticatedRequest<{ projectId: string }>,
|
||||
_res: Response,
|
||||
@Param('dataStoreId') dataStoreId: string,
|
||||
@Body dto: AddDataStoreColumnDto,
|
||||
@Param('dataTableId') dataTableId: string,
|
||||
@Body dto: AddDataTableColumnDto,
|
||||
) {
|
||||
try {
|
||||
return await this.dataStoreService.addColumn(dataStoreId, req.params.projectId, dto);
|
||||
return await this.dataTableService.addColumn(dataTableId, req.params.projectId, dto);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof DataStoreNotFoundError) {
|
||||
if (e instanceof DataTableNotFoundError) {
|
||||
throw new NotFoundError(e.message);
|
||||
} else if (
|
||||
e instanceof DataStoreColumnNameConflictError ||
|
||||
e instanceof DataStoreSystemColumnNameConflictError
|
||||
e instanceof DataTableColumnNameConflictError ||
|
||||
e instanceof DataTableSystemColumnNameConflictError
|
||||
) {
|
||||
throw new ConflictError(e.message);
|
||||
} else if (e instanceof Error) {
|
||||
|
|
@ -164,18 +164,18 @@ export class DataStoreController {
|
|||
}
|
||||
}
|
||||
|
||||
@Delete('/:dataStoreId/columns/:columnId')
|
||||
@Delete('/:dataTableId/columns/:columnId')
|
||||
@ProjectScope('dataStore:update')
|
||||
async deleteColumn(
|
||||
req: AuthenticatedRequest<{ projectId: string }>,
|
||||
_res: Response,
|
||||
@Param('dataStoreId') dataStoreId: string,
|
||||
@Param('dataTableId') dataTableId: string,
|
||||
@Param('columnId') columnId: string,
|
||||
) {
|
||||
try {
|
||||
return await this.dataStoreService.deleteColumn(dataStoreId, req.params.projectId, columnId);
|
||||
return await this.dataTableService.deleteColumn(dataTableId, req.params.projectId, columnId);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof DataStoreNotFoundError || e instanceof DataStoreColumnNotFoundError) {
|
||||
if (e instanceof DataTableNotFoundError || e instanceof DataTableColumnNotFoundError) {
|
||||
throw new NotFoundError(e.message);
|
||||
} else if (e instanceof Error) {
|
||||
throw new InternalServerError(e.message, e);
|
||||
|
|
@ -185,26 +185,26 @@ export class DataStoreController {
|
|||
}
|
||||
}
|
||||
|
||||
@Patch('/:dataStoreId/columns/:columnId/move')
|
||||
@Patch('/:dataTableId/columns/:columnId/move')
|
||||
@ProjectScope('dataStore:update')
|
||||
async moveColumn(
|
||||
req: AuthenticatedRequest<{ projectId: string }>,
|
||||
_res: Response,
|
||||
@Param('dataStoreId') dataStoreId: string,
|
||||
@Param('dataTableId') dataTableId: string,
|
||||
@Param('columnId') columnId: string,
|
||||
@Body dto: MoveDataStoreColumnDto,
|
||||
@Body dto: MoveDataTableColumnDto,
|
||||
) {
|
||||
try {
|
||||
return await this.dataStoreService.moveColumn(
|
||||
dataStoreId,
|
||||
return await this.dataTableService.moveColumn(
|
||||
dataTableId,
|
||||
req.params.projectId,
|
||||
columnId,
|
||||
dto,
|
||||
);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof DataStoreNotFoundError || e instanceof DataStoreColumnNotFoundError) {
|
||||
if (e instanceof DataTableNotFoundError || e instanceof DataTableColumnNotFoundError) {
|
||||
throw new NotFoundError(e.message);
|
||||
} else if (e instanceof DataStoreValidationError) {
|
||||
} else if (e instanceof DataTableValidationError) {
|
||||
throw new BadRequestError(e.message);
|
||||
} else if (e instanceof Error) {
|
||||
throw new InternalServerError(e.message, e);
|
||||
|
|
@ -214,22 +214,22 @@ export class DataStoreController {
|
|||
}
|
||||
}
|
||||
|
||||
@Get('/:dataStoreId/rows')
|
||||
@Get('/:dataTableId/rows')
|
||||
@ProjectScope('dataStore:readRow')
|
||||
async getDataStoreRows(
|
||||
async getDataTableRows(
|
||||
req: AuthenticatedRequest<{ projectId: string }>,
|
||||
_res: Response,
|
||||
@Param('dataStoreId') dataStoreId: string,
|
||||
@Query dto: ListDataStoreContentQueryDto,
|
||||
@Param('dataTableId') dataTableId: string,
|
||||
@Query dto: ListDataTableContentQueryDto,
|
||||
) {
|
||||
try {
|
||||
return await this.dataStoreService.getManyRowsAndCount(
|
||||
dataStoreId,
|
||||
return await this.dataTableService.getManyRowsAndCount(
|
||||
dataTableId,
|
||||
req.params.projectId,
|
||||
dto,
|
||||
);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof DataStoreNotFoundError) {
|
||||
if (e instanceof DataTableNotFoundError) {
|
||||
throw new NotFoundError(e.message);
|
||||
} else if (e instanceof Error) {
|
||||
throw new InternalServerError(e.message, e);
|
||||
|
|
@ -242,31 +242,31 @@ export class DataStoreController {
|
|||
/**
|
||||
* @returns the IDs of the inserted rows
|
||||
*/
|
||||
async appendDataStoreRows<T extends DataStoreRowReturn | undefined>(
|
||||
async appendDataTableRows<T extends DataTableRowReturn | undefined>(
|
||||
req: AuthenticatedRequest<{ projectId: string }>,
|
||||
_res: Response,
|
||||
dataStoreId: string,
|
||||
dto: AddDataStoreRowsDto & { returnType?: T },
|
||||
): Promise<Array<T extends true ? DataStoreRowReturn : Pick<DataStoreRowReturn, 'id'>>>;
|
||||
@Post('/:dataStoreId/insert')
|
||||
dataTableId: string,
|
||||
dto: AddDataTableRowsDto & { returnType?: T },
|
||||
): Promise<Array<T extends true ? DataTableRowReturn : Pick<DataTableRowReturn, 'id'>>>;
|
||||
@Post('/:dataTableId/insert')
|
||||
@ProjectScope('dataStore:writeRow')
|
||||
async appendDataStoreRows(
|
||||
async appendDataTableRows(
|
||||
req: AuthenticatedRequest<{ projectId: string }>,
|
||||
_res: Response,
|
||||
@Param('dataStoreId') dataStoreId: string,
|
||||
@Body dto: AddDataStoreRowsDto,
|
||||
@Param('dataTableId') dataTableId: string,
|
||||
@Body dto: AddDataTableRowsDto,
|
||||
) {
|
||||
try {
|
||||
return await this.dataStoreService.insertRows(
|
||||
dataStoreId,
|
||||
return await this.dataTableService.insertRows(
|
||||
dataTableId,
|
||||
req.params.projectId,
|
||||
dto.data,
|
||||
dto.returnType,
|
||||
);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof DataStoreNotFoundError) {
|
||||
if (e instanceof DataTableNotFoundError) {
|
||||
throw new NotFoundError(e.message);
|
||||
} else if (e instanceof DataStoreValidationError) {
|
||||
} else if (e instanceof DataTableValidationError) {
|
||||
throw new BadRequestError(e.message);
|
||||
} else if (e instanceof Error) {
|
||||
throw new InternalServerError(e.message, e);
|
||||
|
|
@ -276,20 +276,20 @@ export class DataStoreController {
|
|||
}
|
||||
}
|
||||
|
||||
@Post('/:dataStoreId/upsert')
|
||||
@Post('/:dataTableId/upsert')
|
||||
@ProjectScope('dataStore:writeRow')
|
||||
async upsertDataStoreRow(
|
||||
async upsertDataTableRow(
|
||||
req: AuthenticatedRequest<{ projectId: string }>,
|
||||
_res: Response,
|
||||
@Param('dataStoreId') dataStoreId: string,
|
||||
@Body dto: UpsertDataStoreRowDto,
|
||||
@Param('dataTableId') dataTableId: string,
|
||||
@Body dto: UpsertDataTableRowDto,
|
||||
) {
|
||||
try {
|
||||
// because of strict overloads, we need separate paths
|
||||
const dryRun = dto.dryRun;
|
||||
if (dryRun) {
|
||||
return await this.dataStoreService.upsertRow(
|
||||
dataStoreId,
|
||||
return await this.dataTableService.upsertRow(
|
||||
dataTableId,
|
||||
req.params.projectId,
|
||||
dto,
|
||||
true, // we want to always return data for dry runs
|
||||
|
|
@ -299,8 +299,8 @@ export class DataStoreController {
|
|||
|
||||
const returnData = dto.returnData;
|
||||
if (returnData) {
|
||||
return await this.dataStoreService.upsertRow(
|
||||
dataStoreId,
|
||||
return await this.dataTableService.upsertRow(
|
||||
dataTableId,
|
||||
req.params.projectId,
|
||||
dto,
|
||||
returnData,
|
||||
|
|
@ -308,17 +308,17 @@ export class DataStoreController {
|
|||
);
|
||||
}
|
||||
|
||||
return await this.dataStoreService.upsertRow(
|
||||
dataStoreId,
|
||||
return await this.dataTableService.upsertRow(
|
||||
dataTableId,
|
||||
req.params.projectId,
|
||||
dto,
|
||||
returnData,
|
||||
dryRun,
|
||||
);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof DataStoreNotFoundError) {
|
||||
if (e instanceof DataTableNotFoundError) {
|
||||
throw new NotFoundError(e.message);
|
||||
} else if (e instanceof DataStoreValidationError) {
|
||||
} else if (e instanceof DataTableValidationError) {
|
||||
throw new BadRequestError(e.message);
|
||||
} else if (e instanceof Error) {
|
||||
throw new InternalServerError(e.message, e);
|
||||
|
|
@ -328,20 +328,20 @@ export class DataStoreController {
|
|||
}
|
||||
}
|
||||
|
||||
@Patch('/:dataStoreId/rows')
|
||||
@Patch('/:dataTableId/rows')
|
||||
@ProjectScope('dataStore:writeRow')
|
||||
async updateDataStoreRows(
|
||||
async updateDataTableRows(
|
||||
req: AuthenticatedRequest<{ projectId: string }>,
|
||||
_res: Response,
|
||||
@Param('dataStoreId') dataStoreId: string,
|
||||
@Param('dataTableId') dataTableId: string,
|
||||
@Body dto: UpdateDataTableRowDto,
|
||||
) {
|
||||
try {
|
||||
// because of strict overloads, we need separate paths
|
||||
const dryRun = dto.dryRun;
|
||||
if (dryRun) {
|
||||
return await this.dataStoreService.updateRows(
|
||||
dataStoreId,
|
||||
return await this.dataTableService.updateRows(
|
||||
dataTableId,
|
||||
req.params.projectId,
|
||||
dto,
|
||||
true, // we want to always return data for dry runs
|
||||
|
|
@ -351,8 +351,8 @@ export class DataStoreController {
|
|||
|
||||
const returnData = dto.returnData;
|
||||
if (returnData) {
|
||||
return await this.dataStoreService.updateRows(
|
||||
dataStoreId,
|
||||
return await this.dataTableService.updateRows(
|
||||
dataTableId,
|
||||
req.params.projectId,
|
||||
dto,
|
||||
returnData,
|
||||
|
|
@ -360,17 +360,17 @@ export class DataStoreController {
|
|||
);
|
||||
}
|
||||
|
||||
return await this.dataStoreService.updateRows(
|
||||
dataStoreId,
|
||||
return await this.dataTableService.updateRows(
|
||||
dataTableId,
|
||||
req.params.projectId,
|
||||
dto,
|
||||
returnData,
|
||||
dryRun,
|
||||
);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof DataStoreNotFoundError) {
|
||||
if (e instanceof DataTableNotFoundError) {
|
||||
throw new NotFoundError(e.message);
|
||||
} else if (e instanceof DataStoreValidationError) {
|
||||
} else if (e instanceof DataTableValidationError) {
|
||||
throw new BadRequestError(e.message);
|
||||
} else if (e instanceof Error) {
|
||||
throw new InternalServerError(e.message, e);
|
||||
|
|
@ -389,16 +389,16 @@ export class DataStoreController {
|
|||
@Query dto: DeleteDataTableRowsDto,
|
||||
) {
|
||||
try {
|
||||
return await this.dataStoreService.deleteRows(
|
||||
return await this.dataTableService.deleteRows(
|
||||
dataTableId,
|
||||
req.params.projectId,
|
||||
dto,
|
||||
dto.returnData,
|
||||
);
|
||||
} catch (e: unknown) {
|
||||
if (e instanceof DataStoreNotFoundError) {
|
||||
if (e instanceof DataTableNotFoundError) {
|
||||
throw new NotFoundError(e.message);
|
||||
} else if (e instanceof DataStoreValidationError) {
|
||||
} else if (e instanceof DataTableValidationError) {
|
||||
throw new BadRequestError(e.message);
|
||||
} else if (e instanceof Error) {
|
||||
throw new InternalServerError(e.message, e);
|
||||
|
|
@ -5,23 +5,23 @@ import { Container } from '@n8n/di';
|
|||
@BackendModule({ name: 'data-table' })
|
||||
export class DataStoreModule implements ModuleInterface {
|
||||
async init() {
|
||||
await import('./data-store.controller');
|
||||
await import('./data-store-aggregate.controller');
|
||||
await import('./data-table.controller');
|
||||
await import('./data-table-aggregate.controller');
|
||||
|
||||
const { DataStoreService } = await import('./data-store.service');
|
||||
await Container.get(DataStoreService).start();
|
||||
const { DataTableService } = await import('./data-table.service');
|
||||
await Container.get(DataTableService).start();
|
||||
|
||||
const { DataStoreAggregateService } = await import('./data-store-aggregate.service');
|
||||
await Container.get(DataStoreAggregateService).start();
|
||||
const { DataTableAggregateService } = await import('./data-table-aggregate.service');
|
||||
await Container.get(DataTableAggregateService).start();
|
||||
}
|
||||
|
||||
@OnShutdown()
|
||||
async shutdown() {
|
||||
const { DataStoreService } = await import('./data-store.service');
|
||||
await Container.get(DataStoreService).shutdown();
|
||||
const { DataTableService } = await import('./data-table.service');
|
||||
await Container.get(DataTableService).shutdown();
|
||||
|
||||
const { DataStoreAggregateService } = await import('./data-store-aggregate.service');
|
||||
await Container.get(DataStoreAggregateService).start();
|
||||
const { DataTableAggregateService } = await import('./data-table-aggregate.service');
|
||||
await Container.get(DataTableAggregateService).shutdown();
|
||||
}
|
||||
|
||||
async entities() {
|
||||
|
|
@ -32,8 +32,8 @@ export class DataStoreModule implements ModuleInterface {
|
|||
}
|
||||
|
||||
async context() {
|
||||
const { DataStoreProxyService } = await import('./data-store-proxy.service');
|
||||
const { DataTableProxyService } = await import('./data-table-proxy.service');
|
||||
|
||||
return { dataStoreProxyProvider: Container.get(DataStoreProxyService) };
|
||||
return { dataTableProxyProvider: Container.get(DataTableProxyService) };
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
DATA_STORE_COLUMN_ERROR_MESSAGE,
|
||||
type DataStoreCreateColumnSchema,
|
||||
type ListDataStoreQueryDto,
|
||||
DATA_TABLE_COLUMN_ERROR_MESSAGE,
|
||||
type DataTableCreateColumnSchema,
|
||||
type ListDataTableQueryDto,
|
||||
} from '@n8n/api-types';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
import { Project, withTransaction } from '@n8n/db';
|
||||
|
|
@ -10,40 +10,40 @@ import { DataSource, EntityManager, Repository, SelectQueryBuilder } from '@n8n/
|
|||
import { UnexpectedError } from 'n8n-workflow';
|
||||
import type { DataTableInfo, DataTablesSizeData } from 'n8n-workflow';
|
||||
|
||||
import { DataStoreRowsRepository } from './data-store-rows.repository';
|
||||
import { DataStoreUserTableName } from './data-store.types';
|
||||
import { DataTableColumn } from './data-table-column.entity';
|
||||
import { DataTableRowsRepository } from './data-table-rows.repository';
|
||||
import { DataTable } from './data-table.entity';
|
||||
import { DataStoreNameConflictError } from './errors/data-store-name-conflict.error';
|
||||
import { DataStoreValidationError } from './errors/data-store-validation.error';
|
||||
import { DataTableUserTableName } from './data-table.types';
|
||||
import { DataTableNameConflictError } from './errors/data-table-name-conflict.error';
|
||||
import { DataTableValidationError } from './errors/data-table-validation.error';
|
||||
import { isValidColumnName, toTableId, toTableName } from './utils/sql-utils';
|
||||
|
||||
@Service()
|
||||
export class DataStoreRepository extends Repository<DataTable> {
|
||||
export class DataTableRepository extends Repository<DataTable> {
|
||||
constructor(
|
||||
dataSource: DataSource,
|
||||
private dataStoreRowsRepository: DataStoreRowsRepository,
|
||||
private dataTableRowsRepository: DataTableRowsRepository,
|
||||
private readonly globalConfig: GlobalConfig,
|
||||
) {
|
||||
super(DataTable, dataSource.manager);
|
||||
}
|
||||
|
||||
async createDataStore(
|
||||
async createDataTable(
|
||||
projectId: string,
|
||||
name: string,
|
||||
columns: DataStoreCreateColumnSchema[],
|
||||
columns: DataTableCreateColumnSchema[],
|
||||
trx?: EntityManager,
|
||||
) {
|
||||
return await withTransaction(this.manager, trx, async (em) => {
|
||||
if (columns.some((c) => !isValidColumnName(c.name))) {
|
||||
throw new DataStoreValidationError(DATA_STORE_COLUMN_ERROR_MESSAGE);
|
||||
throw new DataTableValidationError(DATA_TABLE_COLUMN_ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
const dataStore = em.create(DataTable, { name, columns, projectId });
|
||||
const dataTable = em.create(DataTable, { name, columns, projectId });
|
||||
|
||||
// @ts-ignore Workaround for intermittent typecheck issue with _QueryDeepPartialEntity
|
||||
await em.insert(DataTable, dataStore);
|
||||
const dataTableId = dataStore.id;
|
||||
await em.insert(DataTable, dataTable);
|
||||
const dataTableId = dataTable.id;
|
||||
|
||||
// insert columns
|
||||
const columnEntities = columns.map((col, index) =>
|
||||
|
|
@ -61,30 +61,30 @@ export class DataStoreRepository extends Repository<DataTable> {
|
|||
}
|
||||
|
||||
// create user table (will create empty table with just id column if no columns)
|
||||
await this.dataStoreRowsRepository.createTableWithColumns(dataTableId, columnEntities, em);
|
||||
await this.dataTableRowsRepository.createTableWithColumns(dataTableId, columnEntities, em);
|
||||
|
||||
if (!dataTableId) {
|
||||
throw new UnexpectedError('Data store creation failed');
|
||||
throw new UnexpectedError('Data table creation failed');
|
||||
}
|
||||
|
||||
const createdDataStore = await em.findOneOrFail(DataTable, {
|
||||
const createdDataTable = await em.findOneOrFail(DataTable, {
|
||||
where: { id: dataTableId },
|
||||
relations: ['project', 'columns'],
|
||||
});
|
||||
|
||||
return createdDataStore;
|
||||
return createdDataTable;
|
||||
});
|
||||
}
|
||||
|
||||
async deleteDataStore(dataStoreId: string, trx?: EntityManager) {
|
||||
async deleteDataTable(dataTableId: string, trx?: EntityManager) {
|
||||
return await withTransaction(this.manager, trx, async (em) => {
|
||||
await em.delete(DataTable, { id: dataStoreId });
|
||||
await this.dataStoreRowsRepository.dropTable(dataStoreId, em);
|
||||
await em.delete(DataTable, { id: dataTableId });
|
||||
await this.dataTableRowsRepository.dropTable(dataTableId, em);
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
async transferDataStoreByProjectId(
|
||||
async transferDataTableByProjectId(
|
||||
fromProjectId: string,
|
||||
toProjectId: string,
|
||||
trx?: EntityManager,
|
||||
|
|
@ -112,8 +112,8 @@ export class DataStoreRepository extends Repository<DataTable> {
|
|||
});
|
||||
|
||||
if (stillHasNameClash) {
|
||||
throw new DataStoreNameConflictError(
|
||||
`Failed to transfer data store "${existing.name}" to the target project "${toProjectId}". A data table with the same name already exists in the target project.`,
|
||||
throw new DataTableNameConflictError(
|
||||
`Failed to transfer data table "${existing.name}" to the target project "${toProjectId}". A data table with the same name already exists in the target project.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -126,13 +126,13 @@ export class DataStoreRepository extends Repository<DataTable> {
|
|||
});
|
||||
}
|
||||
|
||||
async deleteDataStoreByProjectId(projectId: string, trx?: EntityManager) {
|
||||
async deleteDataTableByProjectId(projectId: string, trx?: EntityManager) {
|
||||
return await withTransaction(this.manager, trx, async (em) => {
|
||||
const existingTables = await em.findBy(DataTable, { projectId });
|
||||
|
||||
let changed = false;
|
||||
for (const match of existingTables) {
|
||||
const result = await this.deleteDataStore(match.id, em);
|
||||
const result = await this.deleteDataTable(match.id, em);
|
||||
changed = changed || result;
|
||||
}
|
||||
|
||||
|
|
@ -140,14 +140,14 @@ export class DataStoreRepository extends Repository<DataTable> {
|
|||
});
|
||||
}
|
||||
|
||||
async deleteDataStoreAll(trx?: EntityManager) {
|
||||
async deleteDataTableAll(trx?: EntityManager) {
|
||||
return await withTransaction(this.manager, trx, async (em) => {
|
||||
const existingTables = await em.findBy(DataTable, {});
|
||||
|
||||
let changed = false;
|
||||
for (const match of existingTables) {
|
||||
const result = await em.delete(DataTable, { id: match.id });
|
||||
await this.dataStoreRowsRepository.dropTable(match.id, em);
|
||||
await this.dataTableRowsRepository.dropTable(match.id, em);
|
||||
changed = changed || (result.affected ?? 0) > 0;
|
||||
}
|
||||
|
||||
|
|
@ -155,19 +155,19 @@ export class DataStoreRepository extends Repository<DataTable> {
|
|||
});
|
||||
}
|
||||
|
||||
async getManyAndCount(options: Partial<ListDataStoreQueryDto>) {
|
||||
async getManyAndCount(options: Partial<ListDataTableQueryDto>) {
|
||||
const query = this.getManyQuery(options);
|
||||
const [data, count] = await query.getManyAndCount();
|
||||
return { count, data };
|
||||
}
|
||||
|
||||
async getMany(options: Partial<ListDataStoreQueryDto>) {
|
||||
async getMany(options: Partial<ListDataTableQueryDto>) {
|
||||
const query = this.getManyQuery(options);
|
||||
return await query.getMany();
|
||||
}
|
||||
|
||||
private getManyQuery(options: Partial<ListDataStoreQueryDto>): SelectQueryBuilder<DataTable> {
|
||||
const query = this.createQueryBuilder('dataStore');
|
||||
private getManyQuery(options: Partial<ListDataTableQueryDto>): SelectQueryBuilder<DataTable> {
|
||||
const query = this.createQueryBuilder('dataTable');
|
||||
|
||||
this.applySelections(query);
|
||||
this.applyFilters(query, options.filter);
|
||||
|
|
@ -183,13 +183,13 @@ export class DataStoreRepository extends Repository<DataTable> {
|
|||
|
||||
private applyFilters(
|
||||
query: SelectQueryBuilder<DataTable>,
|
||||
filter: Partial<ListDataStoreQueryDto>['filter'],
|
||||
filter: Partial<ListDataTableQueryDto>['filter'],
|
||||
): void {
|
||||
for (const x of ['id', 'projectId'] as const) {
|
||||
const content = [filter?.[x]].flat().filter((x) => x !== undefined);
|
||||
if (content.length === 0) continue;
|
||||
|
||||
query.andWhere(`dataStore.${x} IN (:...${x}s)`, {
|
||||
query.andWhere(`dataTable.${x} IN (:...${x}s)`, {
|
||||
/*
|
||||
* If list is empty, add a dummy value to prevent an error
|
||||
* when using the IN operator with an empty array.
|
||||
|
|
@ -202,7 +202,7 @@ export class DataStoreRepository extends Repository<DataTable> {
|
|||
const nameFilters = Array.isArray(filter.name) ? filter.name : [filter.name];
|
||||
|
||||
nameFilters.forEach((name, i) => {
|
||||
query.andWhere(`LOWER(dataStore.name) LIKE LOWER(:name${i})`, {
|
||||
query.andWhere(`LOWER(dataTable.name) LIKE LOWER(:name${i})`, {
|
||||
['name' + i]: `%${name}%`,
|
||||
});
|
||||
});
|
||||
|
|
@ -211,7 +211,7 @@ export class DataStoreRepository extends Repository<DataTable> {
|
|||
|
||||
private applySorting(query: SelectQueryBuilder<DataTable>, sortBy?: string): void {
|
||||
if (!sortBy) {
|
||||
query.orderBy('dataStore.updatedAt', 'DESC');
|
||||
query.orderBy('dataTable.updatedAt', 'DESC');
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -231,16 +231,16 @@ export class DataStoreRepository extends Repository<DataTable> {
|
|||
): void {
|
||||
if (field === 'name') {
|
||||
query
|
||||
.addSelect('LOWER(dataStore.name)', 'datastore_name_lower')
|
||||
.orderBy('datastore_name_lower', direction);
|
||||
.addSelect('LOWER(dataTable.name)', 'datatable_name_lower')
|
||||
.orderBy('datatable_name_lower', direction);
|
||||
} else if (['createdAt', 'updatedAt'].includes(field)) {
|
||||
query.orderBy(`dataStore.${field}`, direction);
|
||||
query.orderBy(`dataTable.${field}`, direction);
|
||||
}
|
||||
}
|
||||
|
||||
private applyPagination(
|
||||
query: SelectQueryBuilder<DataTable>,
|
||||
options: Partial<ListDataStoreQueryDto>,
|
||||
options: Partial<ListDataTableQueryDto>,
|
||||
): void {
|
||||
query.skip(options.skip ?? 0);
|
||||
if (options.take !== undefined) query.take(options.take);
|
||||
|
|
@ -248,16 +248,16 @@ export class DataStoreRepository extends Repository<DataTable> {
|
|||
|
||||
private applyDefaultSelect(query: SelectQueryBuilder<DataTable>): void {
|
||||
query
|
||||
.leftJoinAndSelect('dataStore.project', 'project')
|
||||
.leftJoinAndSelect('dataStore.columns', 'data_store_column')
|
||||
.leftJoinAndSelect('dataTable.project', 'project')
|
||||
.leftJoinAndSelect('dataTable.columns', 'data_table_column')
|
||||
.select([
|
||||
'dataStore',
|
||||
...this.getDataStoreColumnFields('data_store_column'),
|
||||
'dataTable',
|
||||
...this.getDataTableColumnFields('data_table_column'),
|
||||
...this.getProjectFields('project'),
|
||||
]);
|
||||
}
|
||||
|
||||
private getDataStoreColumnFields(alias: string): string[] {
|
||||
private getDataTableColumnFields(alias: string): string[] {
|
||||
return [
|
||||
`${alias}.id`,
|
||||
`${alias}.name`,
|
||||
|
|
@ -377,9 +377,9 @@ export class DataStoreRepository extends Repository<DataTable> {
|
|||
|
||||
for (const row of result) {
|
||||
if (row.table_bytes !== null && row.table_name) {
|
||||
const dataStoreId = toTableId(row.table_name as DataStoreUserTableName);
|
||||
const dataTableId = toTableId(row.table_name as DataTableUserTableName);
|
||||
const sizeBytes = this.parseSize(row.table_bytes);
|
||||
sizeMap.set(dataStoreId, (sizeMap.get(dataStoreId) ?? 0) + sizeBytes);
|
||||
sizeMap.set(dataTableId, (sizeMap.get(dataTableId) ?? 0) + sizeBytes);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
import type {
|
||||
AddDataStoreColumnDto,
|
||||
CreateDataStoreDto,
|
||||
AddDataTableColumnDto,
|
||||
CreateDataTableDto,
|
||||
DeleteDataTableRowsDto,
|
||||
ListDataStoreContentQueryDto,
|
||||
MoveDataStoreColumnDto,
|
||||
DataStoreListOptions,
|
||||
UpsertDataStoreRowDto,
|
||||
UpdateDataStoreDto,
|
||||
ListDataTableContentQueryDto,
|
||||
MoveDataTableColumnDto,
|
||||
DataTableListOptions,
|
||||
UpsertDataTableRowDto,
|
||||
UpdateDataTableDto,
|
||||
UpdateDataTableRowDto,
|
||||
} from '@n8n/api-types';
|
||||
import { Logger } from '@n8n/backend-common';
|
||||
|
|
@ -14,42 +14,42 @@ import { ProjectRelationRepository, type User } from '@n8n/db';
|
|||
import { Service } from '@n8n/di';
|
||||
import { DateTime } from 'luxon';
|
||||
import type {
|
||||
DataStoreColumnJsType,
|
||||
DataTableColumnJsType,
|
||||
DataTableFilter,
|
||||
DataStoreRow,
|
||||
DataStoreRowReturn,
|
||||
DataStoreRows,
|
||||
DataTableRow,
|
||||
DataTableRowReturn,
|
||||
DataTableRows,
|
||||
DataTableInsertRowsReturnType,
|
||||
DataTableInsertRowsResult,
|
||||
DataTablesSizeResult,
|
||||
DataTableInfoById,
|
||||
DataStoreColumnType,
|
||||
DataStoreRowReturnWithState,
|
||||
DataTableColumnType,
|
||||
DataTableRowReturnWithState,
|
||||
} from 'n8n-workflow';
|
||||
import { DATA_TABLE_SYSTEM_COLUMN_TYPE_MAP, validateFieldType } from 'n8n-workflow';
|
||||
|
||||
import { RoleService } from '@/services/role.service';
|
||||
|
||||
import { DataStoreColumnRepository } from './data-store-column.repository';
|
||||
import { DataStoreRowsRepository } from './data-store-rows.repository';
|
||||
import { DataStoreSizeValidator } from './data-store-size-validator.service';
|
||||
import { DataStoreRepository } from './data-store.repository';
|
||||
import { columnTypeToFieldType } from './data-store.types';
|
||||
import { DataTableColumn } from './data-table-column.entity';
|
||||
import { DataStoreColumnNotFoundError } from './errors/data-store-column-not-found.error';
|
||||
import { DataStoreNameConflictError } from './errors/data-store-name-conflict.error';
|
||||
import { DataStoreNotFoundError } from './errors/data-store-not-found.error';
|
||||
import { DataStoreValidationError } from './errors/data-store-validation.error';
|
||||
import { DataTableColumnRepository } from './data-table-column.repository';
|
||||
import { DataTableRowsRepository } from './data-table-rows.repository';
|
||||
import { DataTableSizeValidator } from './data-table-size-validator.service';
|
||||
import { DataTableRepository } from './data-table.repository';
|
||||
import { columnTypeToFieldType } from './data-table.types';
|
||||
import { DataTableColumnNotFoundError } from './errors/data-table-column-not-found.error';
|
||||
import { DataTableNameConflictError } from './errors/data-table-name-conflict.error';
|
||||
import { DataTableNotFoundError } from './errors/data-table-not-found.error';
|
||||
import { DataTableValidationError } from './errors/data-table-validation.error';
|
||||
import { normalizeRows } from './utils/sql-utils';
|
||||
|
||||
@Service()
|
||||
export class DataStoreService {
|
||||
export class DataTableService {
|
||||
constructor(
|
||||
private readonly dataStoreRepository: DataStoreRepository,
|
||||
private readonly dataStoreColumnRepository: DataStoreColumnRepository,
|
||||
private readonly dataStoreRowsRepository: DataStoreRowsRepository,
|
||||
private readonly dataTableRepository: DataTableRepository,
|
||||
private readonly dataTableColumnRepository: DataTableColumnRepository,
|
||||
private readonly dataTableRowsRepository: DataTableRowsRepository,
|
||||
private readonly logger: Logger,
|
||||
private readonly dataStoreSizeValidator: DataStoreSizeValidator,
|
||||
private readonly dataTableSizeValidator: DataTableSizeValidator,
|
||||
private readonly projectRelationRepository: ProjectRelationRepository,
|
||||
private readonly roleService: RoleService,
|
||||
) {
|
||||
|
|
@ -59,107 +59,107 @@ export class DataStoreService {
|
|||
async start() {}
|
||||
async shutdown() {}
|
||||
|
||||
async createDataStore(projectId: string, dto: CreateDataStoreDto) {
|
||||
async createDataTable(projectId: string, dto: CreateDataTableDto) {
|
||||
await this.validateUniqueName(dto.name, projectId);
|
||||
|
||||
const result = await this.dataStoreRepository.createDataStore(projectId, dto.name, dto.columns);
|
||||
const result = await this.dataTableRepository.createDataTable(projectId, dto.name, dto.columns);
|
||||
|
||||
this.dataStoreSizeValidator.reset();
|
||||
this.dataTableSizeValidator.reset();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Updates data store properties (currently limited to renaming)
|
||||
async updateDataStore(dataStoreId: string, projectId: string, dto: UpdateDataStoreDto) {
|
||||
await this.validateDataStoreExists(dataStoreId, projectId);
|
||||
// Updates data table properties (currently limited to renaming)
|
||||
async updateDataTable(dataTableId: string, projectId: string, dto: UpdateDataTableDto) {
|
||||
await this.validateDataTableExists(dataTableId, projectId);
|
||||
await this.validateUniqueName(dto.name, projectId);
|
||||
|
||||
await this.dataStoreRepository.update({ id: dataStoreId }, { name: dto.name });
|
||||
await this.dataTableRepository.update({ id: dataTableId }, { name: dto.name });
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async transferDataStoresByProjectId(fromProjectId: string, toProjectId: string) {
|
||||
return await this.dataStoreRepository.transferDataStoreByProjectId(fromProjectId, toProjectId);
|
||||
async transferDataTablesByProjectId(fromProjectId: string, toProjectId: string) {
|
||||
return await this.dataTableRepository.transferDataTableByProjectId(fromProjectId, toProjectId);
|
||||
}
|
||||
|
||||
async deleteDataStoreByProjectId(projectId: string) {
|
||||
const result = await this.dataStoreRepository.deleteDataStoreByProjectId(projectId);
|
||||
async deleteDataTableByProjectId(projectId: string) {
|
||||
const result = await this.dataTableRepository.deleteDataTableByProjectId(projectId);
|
||||
|
||||
if (result) {
|
||||
this.dataStoreSizeValidator.reset();
|
||||
this.dataTableSizeValidator.reset();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async deleteDataStoreAll() {
|
||||
const result = await this.dataStoreRepository.deleteDataStoreAll();
|
||||
async deleteDataTableAll() {
|
||||
const result = await this.dataTableRepository.deleteDataTableAll();
|
||||
|
||||
if (result) {
|
||||
this.dataStoreSizeValidator.reset();
|
||||
this.dataTableSizeValidator.reset();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async deleteDataStore(dataStoreId: string, projectId: string) {
|
||||
await this.validateDataStoreExists(dataStoreId, projectId);
|
||||
async deleteDataTable(dataTableId: string, projectId: string) {
|
||||
await this.validateDataTableExists(dataTableId, projectId);
|
||||
|
||||
await this.dataStoreRepository.deleteDataStore(dataStoreId);
|
||||
await this.dataTableRepository.deleteDataTable(dataTableId);
|
||||
|
||||
this.dataStoreSizeValidator.reset();
|
||||
this.dataTableSizeValidator.reset();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async addColumn(dataStoreId: string, projectId: string, dto: AddDataStoreColumnDto) {
|
||||
await this.validateDataStoreExists(dataStoreId, projectId);
|
||||
async addColumn(dataTableId: string, projectId: string, dto: AddDataTableColumnDto) {
|
||||
await this.validateDataTableExists(dataTableId, projectId);
|
||||
|
||||
return await this.dataStoreColumnRepository.addColumn(dataStoreId, dto);
|
||||
return await this.dataTableColumnRepository.addColumn(dataTableId, dto);
|
||||
}
|
||||
|
||||
async moveColumn(
|
||||
dataStoreId: string,
|
||||
dataTableId: string,
|
||||
projectId: string,
|
||||
columnId: string,
|
||||
dto: MoveDataStoreColumnDto,
|
||||
dto: MoveDataTableColumnDto,
|
||||
) {
|
||||
await this.validateDataStoreExists(dataStoreId, projectId);
|
||||
const existingColumn = await this.validateColumnExists(dataStoreId, columnId);
|
||||
await this.validateDataTableExists(dataTableId, projectId);
|
||||
const existingColumn = await this.validateColumnExists(dataTableId, columnId);
|
||||
|
||||
await this.dataStoreColumnRepository.moveColumn(dataStoreId, existingColumn, dto.targetIndex);
|
||||
await this.dataTableColumnRepository.moveColumn(dataTableId, existingColumn, dto.targetIndex);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async deleteColumn(dataStoreId: string, projectId: string, columnId: string) {
|
||||
await this.validateDataStoreExists(dataStoreId, projectId);
|
||||
const existingColumn = await this.validateColumnExists(dataStoreId, columnId);
|
||||
async deleteColumn(dataTableId: string, projectId: string, columnId: string) {
|
||||
await this.validateDataTableExists(dataTableId, projectId);
|
||||
const existingColumn = await this.validateColumnExists(dataTableId, columnId);
|
||||
|
||||
await this.dataStoreColumnRepository.deleteColumn(dataStoreId, existingColumn);
|
||||
await this.dataTableColumnRepository.deleteColumn(dataTableId, existingColumn);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
async getManyAndCount(options: DataStoreListOptions) {
|
||||
return await this.dataStoreRepository.getManyAndCount(options);
|
||||
async getManyAndCount(options: DataTableListOptions) {
|
||||
return await this.dataTableRepository.getManyAndCount(options);
|
||||
}
|
||||
|
||||
async getManyRowsAndCount(
|
||||
dataStoreId: string,
|
||||
dataTableId: string,
|
||||
projectId: string,
|
||||
dto: ListDataStoreContentQueryDto,
|
||||
dto: ListDataTableContentQueryDto,
|
||||
) {
|
||||
await this.validateDataStoreExists(dataStoreId, projectId);
|
||||
await this.validateDataTableExists(dataTableId, projectId);
|
||||
|
||||
return await this.dataStoreColumnRepository.manager.transaction(async (em) => {
|
||||
const columns = await this.dataStoreColumnRepository.getColumns(dataStoreId, em);
|
||||
return await this.dataTableColumnRepository.manager.transaction(async (em) => {
|
||||
const columns = await this.dataTableColumnRepository.getColumns(dataTableId, em);
|
||||
if (dto.filter) {
|
||||
this.validateAndTransformFilters(dto.filter, columns);
|
||||
}
|
||||
const result = await this.dataStoreRowsRepository.getManyAndCount(
|
||||
dataStoreId,
|
||||
const result = await this.dataTableRowsRepository.getManyAndCount(
|
||||
dataTableId,
|
||||
dto,
|
||||
columns,
|
||||
em,
|
||||
|
|
@ -171,33 +171,33 @@ export class DataStoreService {
|
|||
});
|
||||
}
|
||||
|
||||
async getColumns(dataStoreId: string, projectId: string) {
|
||||
await this.validateDataStoreExists(dataStoreId, projectId);
|
||||
async getColumns(dataTableId: string, projectId: string) {
|
||||
await this.validateDataTableExists(dataTableId, projectId);
|
||||
|
||||
return await this.dataStoreColumnRepository.getColumns(dataStoreId);
|
||||
return await this.dataTableColumnRepository.getColumns(dataTableId);
|
||||
}
|
||||
|
||||
async insertRows<T extends DataTableInsertRowsReturnType = 'count'>(
|
||||
dataStoreId: string,
|
||||
dataTableId: string,
|
||||
projectId: string,
|
||||
rows: DataStoreRows,
|
||||
rows: DataTableRows,
|
||||
returnType?: T,
|
||||
): Promise<DataTableInsertRowsResult<T>>;
|
||||
async insertRows(
|
||||
dataStoreId: string,
|
||||
dataTableId: string,
|
||||
projectId: string,
|
||||
rows: DataStoreRows,
|
||||
rows: DataTableRows,
|
||||
returnType: DataTableInsertRowsReturnType = 'count',
|
||||
) {
|
||||
await this.validateDataTableSize();
|
||||
await this.validateDataStoreExists(dataStoreId, projectId);
|
||||
await this.validateDataTableExists(dataTableId, projectId);
|
||||
|
||||
const result = await this.dataStoreColumnRepository.manager.transaction(async (trx) => {
|
||||
const columns = await this.dataStoreColumnRepository.getColumns(dataStoreId, trx);
|
||||
const result = await this.dataTableColumnRepository.manager.transaction(async (trx) => {
|
||||
const columns = await this.dataTableColumnRepository.getColumns(dataTableId, trx);
|
||||
this.validateRowsWithColumns(rows, columns);
|
||||
|
||||
return await this.dataStoreRowsRepository.insertRows(
|
||||
dataStoreId,
|
||||
return await this.dataTableRowsRepository.insertRows(
|
||||
dataTableId,
|
||||
rows,
|
||||
columns,
|
||||
returnType,
|
||||
|
|
@ -205,7 +205,7 @@ export class DataStoreService {
|
|||
);
|
||||
});
|
||||
|
||||
this.dataStoreSizeValidator.reset();
|
||||
this.dataTableSizeValidator.reset();
|
||||
|
||||
return result;
|
||||
}
|
||||
|
|
@ -213,40 +213,40 @@ export class DataStoreService {
|
|||
async upsertRow<T extends boolean | undefined>(
|
||||
dataTableId: string,
|
||||
projectId: string,
|
||||
dto: Omit<UpsertDataStoreRowDto, 'returnData' | 'dryRun'>,
|
||||
dto: Omit<UpsertDataTableRowDto, 'returnData' | 'dryRun'>,
|
||||
returnData: true,
|
||||
dryRun?: boolean,
|
||||
): Promise<DataStoreRowReturn[] | DataStoreRowReturnWithState[]>;
|
||||
): Promise<DataTableRowReturn[] | DataTableRowReturnWithState[]>;
|
||||
async upsertRow(
|
||||
dataTableId: string,
|
||||
projectId: string,
|
||||
dto: Omit<UpsertDataStoreRowDto, 'returnData' | 'dryRun'>,
|
||||
dto: Omit<UpsertDataTableRowDto, 'returnData' | 'dryRun'>,
|
||||
returnData?: boolean,
|
||||
dryRun?: true,
|
||||
): Promise<DataStoreRowReturnWithState[]>;
|
||||
): Promise<DataTableRowReturnWithState[]>;
|
||||
async upsertRow(
|
||||
dataTableId: string,
|
||||
projectId: string,
|
||||
dto: Omit<UpsertDataStoreRowDto, 'returnData' | 'dryRun'>,
|
||||
dto: Omit<UpsertDataTableRowDto, 'returnData' | 'dryRun'>,
|
||||
returnData?: false,
|
||||
dryRun?: false,
|
||||
): Promise<true>;
|
||||
async upsertRow(
|
||||
dataTableId: string,
|
||||
projectId: string,
|
||||
dto: Omit<UpsertDataStoreRowDto, 'returnData' | 'dryRun'>,
|
||||
dto: Omit<UpsertDataTableRowDto, 'returnData' | 'dryRun'>,
|
||||
returnData: boolean = false,
|
||||
dryRun: boolean = false,
|
||||
) {
|
||||
await this.validateDataTableSize();
|
||||
await this.validateDataStoreExists(dataTableId, projectId);
|
||||
await this.validateDataTableExists(dataTableId, projectId);
|
||||
|
||||
const result = await this.dataStoreColumnRepository.manager.transaction(async (trx) => {
|
||||
const columns = await this.dataStoreColumnRepository.getColumns(dataTableId, trx);
|
||||
const result = await this.dataTableColumnRepository.manager.transaction(async (trx) => {
|
||||
const columns = await this.dataTableColumnRepository.getColumns(dataTableId, trx);
|
||||
this.validateUpdateParams(dto, columns);
|
||||
|
||||
if (dryRun) {
|
||||
return await this.dataStoreRowsRepository.dryRunUpsertRow(
|
||||
return await this.dataTableRowsRepository.dryRunUpsertRow(
|
||||
dataTableId,
|
||||
dto.data,
|
||||
dto.filter,
|
||||
|
|
@ -255,7 +255,7 @@ export class DataStoreService {
|
|||
);
|
||||
}
|
||||
|
||||
const updated = await this.dataStoreRowsRepository.updateRows(
|
||||
const updated = await this.dataTableRowsRepository.updateRows(
|
||||
dataTableId,
|
||||
dto.data,
|
||||
dto.filter,
|
||||
|
|
@ -269,7 +269,7 @@ export class DataStoreService {
|
|||
}
|
||||
|
||||
// No rows were updated, so insert a new one
|
||||
const inserted = await this.dataStoreRowsRepository.insertRows(
|
||||
const inserted = await this.dataTableRowsRepository.insertRows(
|
||||
dataTableId,
|
||||
[dto.data],
|
||||
columns,
|
||||
|
|
@ -280,7 +280,7 @@ export class DataStoreService {
|
|||
});
|
||||
|
||||
if (!dryRun) {
|
||||
this.dataStoreSizeValidator.reset();
|
||||
this.dataTableSizeValidator.reset();
|
||||
}
|
||||
|
||||
return result;
|
||||
|
|
@ -291,16 +291,16 @@ export class DataStoreService {
|
|||
columns: DataTableColumn[],
|
||||
) {
|
||||
if (columns.length === 0) {
|
||||
throw new DataStoreValidationError(
|
||||
throw new DataTableValidationError(
|
||||
'No columns found for this data table or data table not found',
|
||||
);
|
||||
}
|
||||
|
||||
if (!filter?.filters || filter.filters.length === 0) {
|
||||
throw new DataStoreValidationError('Filter must not be empty');
|
||||
throw new DataTableValidationError('Filter must not be empty');
|
||||
}
|
||||
if (!data || Object.keys(data).length === 0) {
|
||||
throw new DataStoreValidationError('Data columns must not be empty');
|
||||
throw new DataTableValidationError('Data columns must not be empty');
|
||||
}
|
||||
|
||||
this.validateRowsWithColumns([data], columns, false);
|
||||
|
|
@ -313,14 +313,14 @@ export class DataStoreService {
|
|||
dto: Omit<UpdateDataTableRowDto, 'returnData' | 'dryRun'>,
|
||||
returnData: true,
|
||||
dryRun?: boolean,
|
||||
): Promise<DataStoreRowReturn[] | DataStoreRowReturnWithState[]>;
|
||||
): Promise<DataTableRowReturn[] | DataTableRowReturnWithState[]>;
|
||||
async updateRows(
|
||||
dataTableId: string,
|
||||
projectId: string,
|
||||
dto: Omit<UpdateDataTableRowDto, 'returnData' | 'dryRun'>,
|
||||
returnData?: boolean,
|
||||
dryRun?: true,
|
||||
): Promise<DataStoreRowReturnWithState[]>;
|
||||
): Promise<DataTableRowReturnWithState[]>;
|
||||
async updateRows(
|
||||
dataTableId: string,
|
||||
projectId: string,
|
||||
|
|
@ -336,14 +336,14 @@ export class DataStoreService {
|
|||
dryRun: boolean = false,
|
||||
) {
|
||||
await this.validateDataTableSize();
|
||||
await this.validateDataStoreExists(dataTableId, projectId);
|
||||
await this.validateDataTableExists(dataTableId, projectId);
|
||||
|
||||
const result = await this.dataStoreColumnRepository.manager.transaction(async (trx) => {
|
||||
const columns = await this.dataStoreColumnRepository.getColumns(dataTableId, trx);
|
||||
const result = await this.dataTableColumnRepository.manager.transaction(async (trx) => {
|
||||
const columns = await this.dataTableColumnRepository.getColumns(dataTableId, trx);
|
||||
this.validateUpdateParams(dto, columns);
|
||||
|
||||
if (dryRun) {
|
||||
return await this.dataStoreRowsRepository.dryRunUpdateRows(
|
||||
return await this.dataTableRowsRepository.dryRunUpdateRows(
|
||||
dataTableId,
|
||||
dto.data,
|
||||
dto.filter,
|
||||
|
|
@ -352,7 +352,7 @@ export class DataStoreService {
|
|||
);
|
||||
}
|
||||
|
||||
return await this.dataStoreRowsRepository.updateRows(
|
||||
return await this.dataTableRowsRepository.updateRows(
|
||||
dataTableId,
|
||||
dto.data,
|
||||
dto.filter,
|
||||
|
|
@ -363,55 +363,55 @@ export class DataStoreService {
|
|||
});
|
||||
|
||||
if (!dryRun) {
|
||||
this.dataStoreSizeValidator.reset();
|
||||
this.dataTableSizeValidator.reset();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
async deleteRows(
|
||||
dataStoreId: string,
|
||||
dataTableId: string,
|
||||
projectId: string,
|
||||
dto: Omit<DeleteDataTableRowsDto, 'returnData' | 'dryRun'>,
|
||||
returnData: true,
|
||||
dryRun?: boolean,
|
||||
): Promise<DataStoreRowReturn[]>;
|
||||
): Promise<DataTableRowReturn[]>;
|
||||
async deleteRows(
|
||||
dataStoreId: string,
|
||||
dataTableId: string,
|
||||
projectId: string,
|
||||
dto: Omit<DeleteDataTableRowsDto, 'returnData' | 'dryRun'>,
|
||||
returnData?: boolean,
|
||||
dryRun?: true,
|
||||
): Promise<DataStoreRowReturn[]>;
|
||||
): Promise<DataTableRowReturn[]>;
|
||||
async deleteRows(
|
||||
dataStoreId: string,
|
||||
dataTableId: string,
|
||||
projectId: string,
|
||||
dto: Omit<DeleteDataTableRowsDto, 'returnData' | 'dryRun'>,
|
||||
returnData?: false,
|
||||
dryRun?: false,
|
||||
): Promise<true>;
|
||||
async deleteRows(
|
||||
dataStoreId: string,
|
||||
dataTableId: string,
|
||||
projectId: string,
|
||||
dto: Omit<DeleteDataTableRowsDto, 'returnData' | 'dryRun'>,
|
||||
returnData: boolean = false,
|
||||
dryRun: boolean = false,
|
||||
) {
|
||||
await this.validateDataStoreExists(dataStoreId, projectId);
|
||||
await this.validateDataTableExists(dataTableId, projectId);
|
||||
|
||||
const result = await this.dataStoreColumnRepository.manager.transaction(async (trx) => {
|
||||
const columns = await this.dataStoreColumnRepository.getColumns(dataStoreId, trx);
|
||||
const result = await this.dataTableColumnRepository.manager.transaction(async (trx) => {
|
||||
const columns = await this.dataTableColumnRepository.getColumns(dataTableId, trx);
|
||||
|
||||
if (!dto.filter?.filters || dto.filter.filters.length === 0) {
|
||||
throw new DataStoreValidationError(
|
||||
throw new DataTableValidationError(
|
||||
'Filter is required for delete operations to prevent accidental deletion of all data',
|
||||
);
|
||||
}
|
||||
|
||||
this.validateAndTransformFilters(dto.filter, columns);
|
||||
|
||||
return await this.dataStoreRowsRepository.deleteRows(
|
||||
dataStoreId,
|
||||
return await this.dataTableRowsRepository.deleteRows(
|
||||
dataTableId,
|
||||
columns,
|
||||
dto.filter,
|
||||
returnData,
|
||||
|
|
@ -421,15 +421,15 @@ export class DataStoreService {
|
|||
});
|
||||
|
||||
if (!dryRun) {
|
||||
this.dataStoreSizeValidator.reset();
|
||||
this.dataTableSizeValidator.reset();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
private validateRowsWithColumns(
|
||||
rows: DataStoreRows,
|
||||
columns: Array<{ name: string; type: DataStoreColumnType }>,
|
||||
rows: DataTableRows,
|
||||
columns: Array<{ name: string; type: DataTableColumnType }>,
|
||||
includeSystemColumns = false,
|
||||
): void {
|
||||
// Include system columns like 'id' if requested
|
||||
|
|
@ -448,14 +448,14 @@ export class DataStoreService {
|
|||
const keys = Object.keys(row);
|
||||
for (const key of keys) {
|
||||
if (!columnNames.has(key)) {
|
||||
throw new DataStoreValidationError(`unknown column name '${key}'`);
|
||||
throw new DataTableValidationError(`unknown column name '${key}'`);
|
||||
}
|
||||
this.validateCell(row, key, columnTypeMap);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private validateCell(row: DataStoreRow, key: string, columnTypeMap: Map<string, string>) {
|
||||
private validateCell(row: DataTableRow, key: string, columnTypeMap: Map<string, string>) {
|
||||
const cell = row[key];
|
||||
if (cell === null) return;
|
||||
|
||||
|
|
@ -471,7 +471,7 @@ export class DataStoreService {
|
|||
});
|
||||
|
||||
if (!validationResult.valid) {
|
||||
throw new DataStoreValidationError(
|
||||
throw new DataTableValidationError(
|
||||
`value '${String(cell)}' does not match column type '${columnType}': ${validationResult.errorMessage}`,
|
||||
);
|
||||
}
|
||||
|
|
@ -483,51 +483,51 @@ export class DataStoreService {
|
|||
row[key] = dateInISO;
|
||||
return;
|
||||
} catch {
|
||||
throw new DataStoreValidationError(
|
||||
throw new DataTableValidationError(
|
||||
`value '${String(cell)}' does not match column type 'date'`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
row[key] = validationResult.newValue as DataStoreColumnJsType;
|
||||
row[key] = validationResult.newValue as DataTableColumnJsType;
|
||||
}
|
||||
|
||||
private async validateDataStoreExists(dataStoreId: string, projectId: string) {
|
||||
const existingTable = await this.dataStoreRepository.findOneBy({
|
||||
id: dataStoreId,
|
||||
private async validateDataTableExists(dataTableId: string, projectId: string) {
|
||||
const existingTable = await this.dataTableRepository.findOneBy({
|
||||
id: dataTableId,
|
||||
project: {
|
||||
id: projectId,
|
||||
},
|
||||
});
|
||||
|
||||
if (!existingTable) {
|
||||
throw new DataStoreNotFoundError(dataStoreId);
|
||||
throw new DataTableNotFoundError(dataTableId);
|
||||
}
|
||||
|
||||
return existingTable;
|
||||
}
|
||||
|
||||
private async validateColumnExists(dataTableId: string, columnId: string) {
|
||||
const existingColumn = await this.dataStoreColumnRepository.findOneBy({
|
||||
const existingColumn = await this.dataTableColumnRepository.findOneBy({
|
||||
id: columnId,
|
||||
dataTableId,
|
||||
});
|
||||
|
||||
if (existingColumn === null) {
|
||||
throw new DataStoreColumnNotFoundError(dataTableId, columnId);
|
||||
throw new DataTableColumnNotFoundError(dataTableId, columnId);
|
||||
}
|
||||
|
||||
return existingColumn;
|
||||
}
|
||||
|
||||
private async validateUniqueName(name: string, projectId: string) {
|
||||
const hasNameClash = await this.dataStoreRepository.existsBy({
|
||||
const hasNameClash = await this.dataTableRepository.existsBy({
|
||||
name,
|
||||
projectId,
|
||||
});
|
||||
|
||||
if (hasNameClash) {
|
||||
throw new DataStoreNameConflictError(name);
|
||||
throw new DataTableNameConflictError(name);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -548,12 +548,12 @@ export class DataStoreService {
|
|||
for (const filter of filterObject.filters) {
|
||||
if (['like', 'ilike'].includes(filter.condition)) {
|
||||
if (filter.value === null || filter.value === undefined) {
|
||||
throw new DataStoreValidationError(
|
||||
throw new DataTableValidationError(
|
||||
`${filter.condition.toUpperCase()} filter value cannot be null or undefined`,
|
||||
);
|
||||
}
|
||||
if (typeof filter.value !== 'string') {
|
||||
throw new DataStoreValidationError(
|
||||
throw new DataTableValidationError(
|
||||
`${filter.condition.toUpperCase()} filter value must be a string`,
|
||||
);
|
||||
}
|
||||
|
|
@ -565,7 +565,7 @@ export class DataStoreService {
|
|||
|
||||
if (['gt', 'gte', 'lt', 'lte'].includes(filter.condition)) {
|
||||
if (filter.value === null || filter.value === undefined) {
|
||||
throw new DataStoreValidationError(
|
||||
throw new DataTableValidationError(
|
||||
`${filter.condition.toUpperCase()} filter value cannot be null or undefined`,
|
||||
);
|
||||
}
|
||||
|
|
@ -574,14 +574,14 @@ export class DataStoreService {
|
|||
}
|
||||
|
||||
private async validateDataTableSize() {
|
||||
await this.dataStoreSizeValidator.validateSize(
|
||||
async () => await this.dataStoreRepository.findDataTablesSize(),
|
||||
await this.dataTableSizeValidator.validateSize(
|
||||
async () => await this.dataTableRepository.findDataTablesSize(),
|
||||
);
|
||||
}
|
||||
|
||||
async getDataTablesSize(user: User): Promise<DataTablesSizeResult> {
|
||||
const allSizeData = await this.dataStoreSizeValidator.getCachedSizeData(
|
||||
async () => await this.dataStoreRepository.findDataTablesSize(),
|
||||
const allSizeData = await this.dataTableSizeValidator.getCachedSizeData(
|
||||
async () => await this.dataTableRepository.findDataTablesSize(),
|
||||
);
|
||||
|
||||
const roles = await this.roleService.rolesWithScope('project', ['dataStore:listProject']);
|
||||
|
|
@ -602,7 +602,7 @@ export class DataStoreService {
|
|||
|
||||
return {
|
||||
totalBytes: allSizeData.totalBytes,
|
||||
quotaStatus: this.dataStoreSizeValidator.sizeToState(allSizeData.totalBytes),
|
||||
quotaStatus: this.dataTableSizeValidator.sizeToState(allSizeData.totalBytes),
|
||||
dataTables: accessibleDataTables,
|
||||
};
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import type { FieldTypeMap } from 'n8n-workflow';
|
||||
|
||||
export type DataStoreUserTableName = `${string}data_table_user_${string}`;
|
||||
export type DataTableUserTableName = `${string}data_table_user_${string}`;
|
||||
|
||||
export const columnTypeToFieldType: Record<string, keyof FieldTypeMap> = {
|
||||
// eslint-disable-next-line id-denylist
|
||||
|
|
@ -1,12 +0,0 @@
|
|||
import { UserError } from 'n8n-workflow';
|
||||
|
||||
export class DataStoreColumnNameConflictError extends UserError {
|
||||
constructor(columnName: string, dataStoreName: string) {
|
||||
super(
|
||||
`Data store column with name '${columnName}' already exists in data store '${dataStoreName}'`,
|
||||
{
|
||||
level: 'warning',
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
import { UserError } from 'n8n-workflow';
|
||||
|
||||
export class DataStoreColumnNotFoundError extends UserError {
|
||||
constructor(dataStoreId: string, columnId: string) {
|
||||
super(`Could not find the column '${columnId}' in the data store: ${dataStoreId}`, {
|
||||
level: 'warning',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
import { UserError } from 'n8n-workflow';
|
||||
|
||||
export class DataStoreNameConflictError extends UserError {
|
||||
constructor(name: string) {
|
||||
super(`Data store with name '${name}' already exists in this project`, {
|
||||
level: 'warning',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
import { UserError } from 'n8n-workflow';
|
||||
|
||||
export class DataStoreNotFoundError extends UserError {
|
||||
constructor(dataStoreId: string) {
|
||||
super(`Could not find the data table: '${dataStoreId}'`, {
|
||||
level: 'warning',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
import { UserError } from 'n8n-workflow';
|
||||
|
||||
export class DataStoreValidationError extends UserError {
|
||||
constructor(msg: string) {
|
||||
super(`Validation error with data store request: ${msg}`, {
|
||||
level: 'warning',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
import { UserError } from 'n8n-workflow';
|
||||
|
||||
export class DataTableColumnNameConflictError extends UserError {
|
||||
constructor(columnName: string, dataTableName: string) {
|
||||
super(
|
||||
`Data table column with name '${columnName}' already exists in data table '${dataTableName}'`,
|
||||
{
|
||||
level: 'warning',
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { UserError } from 'n8n-workflow';
|
||||
|
||||
export class DataTableColumnNotFoundError extends UserError {
|
||||
constructor(dataTableId: string, columnId: string) {
|
||||
super(`Could not find the column '${columnId}' in the data table: ${dataTableId}`, {
|
||||
level: 'warning',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { UserError } from 'n8n-workflow';
|
||||
|
||||
export class DataTableNameConflictError extends UserError {
|
||||
constructor(name: string) {
|
||||
super(`Data table with name '${name}' already exists in this project`, {
|
||||
level: 'warning',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { UserError } from 'n8n-workflow';
|
||||
|
||||
export class DataTableNotFoundError extends UserError {
|
||||
constructor(dataTableId: string) {
|
||||
super(`Could not find the data table: '${dataTableId}'`, {
|
||||
level: 'warning',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
import { UserError } from 'n8n-workflow';
|
||||
|
||||
export class DataStoreSystemColumnNameConflictError extends UserError {
|
||||
export class DataTableSystemColumnNameConflictError extends UserError {
|
||||
constructor(columnName: string, type: string = 'system') {
|
||||
super(`Column name "${columnName}" is reserved as a ${type} column name.`, {
|
||||
level: 'warning',
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
import { UserError } from 'n8n-workflow';
|
||||
|
||||
export class DataTableValidationError extends UserError {
|
||||
constructor(msg: string) {
|
||||
super(`Validation error with data table request: ${msg}`, {
|
||||
level: 'warning',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -1,21 +1,21 @@
|
|||
import {
|
||||
dataStoreColumnNameSchema,
|
||||
DATA_STORE_COLUMN_ERROR_MESSAGE,
|
||||
type DataStoreCreateColumnSchema,
|
||||
dataTableColumnNameSchema,
|
||||
DATA_TABLE_COLUMN_ERROR_MESSAGE,
|
||||
type DataTableCreateColumnSchema,
|
||||
} from '@n8n/api-types';
|
||||
import { GlobalConfig } from '@n8n/config';
|
||||
import { DslColumn } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import type { DataSourceOptions } from '@n8n/typeorm';
|
||||
import type { DataStoreColumnJsType, DataStoreRowReturn, DataStoreRowsReturn } from 'n8n-workflow';
|
||||
import type { DataTableColumnJsType, DataTableRowReturn, DataTableRowsReturn } from 'n8n-workflow';
|
||||
import { UnexpectedError } from 'n8n-workflow';
|
||||
|
||||
import { NotFoundError } from '@/errors/response-errors/not-found.error';
|
||||
|
||||
import type { DataStoreUserTableName } from '../data-store.types';
|
||||
import type { DataTableColumn } from '../data-table-column.entity';
|
||||
import type { DataTableUserTableName } from '../data-table.types';
|
||||
|
||||
export function toDslColumns(columns: DataStoreCreateColumnSchema[]): DslColumn[] {
|
||||
export function toDslColumns(columns: DataTableCreateColumnSchema[]): DslColumn[] {
|
||||
return columns.map((col) => {
|
||||
const name = new DslColumn(col.name.trim());
|
||||
|
||||
|
|
@ -34,8 +34,8 @@ export function toDslColumns(columns: DataStoreCreateColumnSchema[]): DslColumn[
|
|||
});
|
||||
}
|
||||
|
||||
function dataStoreColumnTypeToSql(
|
||||
type: DataStoreCreateColumnSchema['type'],
|
||||
function dataTableColumnTypeToSql(
|
||||
type: DataTableCreateColumnSchema['type'],
|
||||
dbType: DataSourceOptions['type'],
|
||||
) {
|
||||
switch (type) {
|
||||
|
|
@ -66,24 +66,24 @@ function dataStoreColumnTypeToSql(
|
|||
}
|
||||
|
||||
function columnToWildcardAndType(
|
||||
column: DataStoreCreateColumnSchema,
|
||||
column: DataTableCreateColumnSchema,
|
||||
dbType: DataSourceOptions['type'],
|
||||
) {
|
||||
return `${quoteIdentifier(column.name, dbType)} ${dataStoreColumnTypeToSql(column.type, dbType)}`;
|
||||
return `${quoteIdentifier(column.name, dbType)} ${dataTableColumnTypeToSql(column.type, dbType)}`;
|
||||
}
|
||||
|
||||
export function isValidColumnName(name: string) {
|
||||
return dataStoreColumnNameSchema.safeParse(name).success;
|
||||
return dataTableColumnNameSchema.safeParse(name).success;
|
||||
}
|
||||
|
||||
export function addColumnQuery(
|
||||
tableName: DataStoreUserTableName,
|
||||
column: DataStoreCreateColumnSchema,
|
||||
tableName: DataTableUserTableName,
|
||||
column: DataTableCreateColumnSchema,
|
||||
dbType: DataSourceOptions['type'],
|
||||
) {
|
||||
// API requests should already conform to this, but better safe than sorry
|
||||
if (!isValidColumnName(column.name)) {
|
||||
throw new UnexpectedError(DATA_STORE_COLUMN_ERROR_MESSAGE);
|
||||
throw new UnexpectedError(DATA_TABLE_COLUMN_ERROR_MESSAGE);
|
||||
}
|
||||
|
||||
const quotedTableName = quoteIdentifier(tableName, dbType);
|
||||
|
|
@ -92,7 +92,7 @@ export function addColumnQuery(
|
|||
}
|
||||
|
||||
export function deleteColumnQuery(
|
||||
tableName: DataStoreUserTableName,
|
||||
tableName: DataTableUserTableName,
|
||||
column: string,
|
||||
dbType: DataSourceOptions['type'],
|
||||
): string {
|
||||
|
|
@ -129,7 +129,7 @@ function hasInsertId(data: unknown): data is WithInsertId {
|
|||
return typeof data === 'object' && data !== null && 'insertId' in data && isNumber(data.insertId);
|
||||
}
|
||||
|
||||
function hasRowReturnData(data: unknown): data is DataStoreRowReturn {
|
||||
function hasRowReturnData(data: unknown): data is DataTableRowReturn {
|
||||
return (
|
||||
typeof data === 'object' &&
|
||||
data !== null &&
|
||||
|
|
@ -142,11 +142,11 @@ function hasRowReturnData(data: unknown): data is DataStoreRowReturn {
|
|||
);
|
||||
}
|
||||
|
||||
function hasRowId(data: unknown): data is Pick<DataStoreRowReturn, 'id'> {
|
||||
function hasRowId(data: unknown): data is Pick<DataTableRowReturn, 'id'> {
|
||||
return typeof data === 'object' && data !== null && 'id' in data && isNumber(data.id);
|
||||
}
|
||||
|
||||
export function extractReturningData(raw: unknown): DataStoreRowReturn[] {
|
||||
export function extractReturningData(raw: unknown): DataTableRowReturn[] {
|
||||
if (!isArrayOf(raw, hasRowReturnData)) {
|
||||
throw new UnexpectedError(
|
||||
`Expected INSERT INTO raw to be { id: number; createdAt: string; updatedAt: string }[] on Postgres or MariaDB. Is '${JSON.stringify(raw)}'`,
|
||||
|
|
@ -183,7 +183,7 @@ export function extractInsertedIds(raw: unknown, dbType: DataSourceOptions['type
|
|||
}
|
||||
}
|
||||
|
||||
export function normalizeRows(rows: DataStoreRowsReturn, columns: DataTableColumn[]) {
|
||||
export function normalizeRows(rows: DataTableRowsReturn, columns: DataTableColumn[]) {
|
||||
// we need to normalize system dates as well
|
||||
const systemColumns = [
|
||||
{ name: 'createdAt', type: 'date' },
|
||||
|
|
@ -242,10 +242,10 @@ function formatDateForDatabase(date: Date, dbType?: DataSourceOptions['type']):
|
|||
}
|
||||
|
||||
export function normalizeValue(
|
||||
value: DataStoreColumnJsType,
|
||||
value: DataTableColumnJsType,
|
||||
columnType: string | undefined,
|
||||
dbType?: DataSourceOptions['type'],
|
||||
): DataStoreColumnJsType {
|
||||
): DataTableColumnJsType {
|
||||
if (columnType !== 'date' || value === null || value === undefined) {
|
||||
return value;
|
||||
}
|
||||
|
|
@ -292,11 +292,11 @@ export function escapeLikeSpecials(input: string): string {
|
|||
.replace(/_/g, '\\_'); // make '_' literal ('%' stays a wildcard)
|
||||
}
|
||||
|
||||
export function toTableName(dataStoreId: string): DataStoreUserTableName {
|
||||
export function toTableName(dataTableId: string): DataTableUserTableName {
|
||||
const { tablePrefix } = Container.get(GlobalConfig).database;
|
||||
return `${tablePrefix}data_table_user_${dataStoreId}`;
|
||||
return `${tablePrefix}data_table_user_${dataTableId}`;
|
||||
}
|
||||
|
||||
export function toTableId(tableName: DataStoreUserTableName) {
|
||||
export function toTableId(tableName: DataTableUserTableName) {
|
||||
return tableName.replace(/.*data_table_user_/, '');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ import { CredentialsHelper } from '@/credentials-helper';
|
|||
import { VariablesService } from '@/environments.ee/variables/variables.service.ee';
|
||||
import { ExternalHooks } from '@/external-hooks';
|
||||
import type { ManualExecutionService } from '@/manual-execution.service';
|
||||
import { DataStoreProxyService } from '@/modules/data-table/data-store-proxy.service';
|
||||
import { DataTableProxyService } from '@/modules/data-table/data-table-proxy.service';
|
||||
import { WorkflowStatisticsService } from '@/services/workflow-statistics.service';
|
||||
import * as WorkflowExecuteAdditionalData from '@/workflow-execute-additional-data';
|
||||
import { WorkflowStaticDataService } from '@/workflows/workflow-static-data.service';
|
||||
|
|
@ -34,7 +34,7 @@ mockInstance(ExternalSecretsProxy);
|
|||
mockInstance(WorkflowStaticDataService);
|
||||
mockInstance(WorkflowStatisticsService);
|
||||
mockInstance(ExternalHooks);
|
||||
mockInstance(DataStoreProxyService);
|
||||
mockInstance(DataTableProxyService);
|
||||
|
||||
const processRunExecutionDataMock = jest.fn();
|
||||
jest.mock('n8n-core', () => {
|
||||
|
|
|
|||
|
|
@ -95,8 +95,8 @@ export class ProjectService {
|
|||
}
|
||||
|
||||
private get dataTableService() {
|
||||
return import('@/modules/data-table/data-store.service').then(({ DataStoreService }) =>
|
||||
Container.get(DataStoreService),
|
||||
return import('@/modules/data-table/data-table.service').then(({ DataTableService }) =>
|
||||
Container.get(DataTableService),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -189,9 +189,9 @@ export class ProjectService {
|
|||
const dataTableService = await this.dataTableService;
|
||||
|
||||
if (targetProject) {
|
||||
await dataTableService.transferDataStoresByProjectId(project.id, targetProject.id);
|
||||
await dataTableService.transferDataTablesByProjectId(project.id, targetProject.id);
|
||||
} else {
|
||||
await dataTableService.deleteDataStoreByProjectId(project.id);
|
||||
await dataTableService.deleteDataTableByProjectId(project.id);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,43 +0,0 @@
|
|||
import type { CreateDataStoreColumnDto } from '@n8n/api-types';
|
||||
import { randomName } from '@n8n/backend-test-utils';
|
||||
import type { Project } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import type { DataStoreRows } from 'n8n-workflow';
|
||||
|
||||
import { DataStoreColumnRepository } from '@/modules/data-table/data-store-column.repository';
|
||||
import { DataStoreRowsRepository } from '@/modules/data-table/data-store-rows.repository';
|
||||
import { DataStoreRepository } from '@/modules/data-table/data-store.repository';
|
||||
|
||||
export const createDataStore = async (
|
||||
project: Project,
|
||||
options: {
|
||||
name?: string;
|
||||
columns?: CreateDataStoreColumnDto[];
|
||||
data?: DataStoreRows;
|
||||
updatedAt?: Date;
|
||||
} = {},
|
||||
) => {
|
||||
const dataStoreRepository = Container.get(DataStoreRepository);
|
||||
const dataStore = await dataStoreRepository.createDataStore(
|
||||
project.id,
|
||||
options.name ?? randomName(),
|
||||
options.columns ?? [],
|
||||
);
|
||||
|
||||
if (options.updatedAt) {
|
||||
await dataStoreRepository.update(dataStore.id, {
|
||||
updatedAt: options.updatedAt,
|
||||
});
|
||||
dataStore.updatedAt = options.updatedAt;
|
||||
}
|
||||
|
||||
if (options.data) {
|
||||
const dataStoreColumnRepository = Container.get(DataStoreColumnRepository);
|
||||
const columns = await dataStoreColumnRepository.getColumns(dataStore.id);
|
||||
|
||||
const dataStoreRowsRepository = Container.get(DataStoreRowsRepository);
|
||||
await dataStoreRowsRepository.insertRows(dataStore.id, options.data, columns, 'count');
|
||||
}
|
||||
|
||||
return dataStore;
|
||||
};
|
||||
43
packages/cli/test/integration/shared/db/data-tables.ts
Normal file
43
packages/cli/test/integration/shared/db/data-tables.ts
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
import type { CreateDataTableColumnDto } from '@n8n/api-types';
|
||||
import { randomName } from '@n8n/backend-test-utils';
|
||||
import type { Project } from '@n8n/db';
|
||||
import { Container } from '@n8n/di';
|
||||
import type { DataTableRows } from 'n8n-workflow';
|
||||
|
||||
import { DataTableColumnRepository } from '@/modules/data-table/data-table-column.repository';
|
||||
import { DataTableRowsRepository } from '@/modules/data-table/data-table-rows.repository';
|
||||
import { DataTableRepository } from '@/modules/data-table/data-table.repository';
|
||||
|
||||
export const createDataTable = async (
|
||||
project: Project,
|
||||
options: {
|
||||
name?: string;
|
||||
columns?: CreateDataTableColumnDto[];
|
||||
data?: DataTableRows;
|
||||
updatedAt?: Date;
|
||||
} = {},
|
||||
) => {
|
||||
const dataTableRepository = Container.get(DataTableRepository);
|
||||
const dataTable = await dataTableRepository.createDataTable(
|
||||
project.id,
|
||||
options.name ?? randomName(),
|
||||
options.columns ?? [],
|
||||
);
|
||||
|
||||
if (options.updatedAt) {
|
||||
await dataTableRepository.update(dataTable.id, {
|
||||
updatedAt: options.updatedAt,
|
||||
});
|
||||
dataTable.updatedAt = options.updatedAt;
|
||||
}
|
||||
|
||||
if (options.data) {
|
||||
const dataTableColumnRepository = Container.get(DataTableColumnRepository);
|
||||
const columns = await dataTableColumnRepository.getColumns(dataTable.id);
|
||||
|
||||
const dataTableRowsRepository = Container.get(DataTableRowsRepository);
|
||||
await dataTableRowsRepository.insertRows(dataTable.id, options.data, columns, 'count');
|
||||
}
|
||||
|
||||
return dataTable;
|
||||
};
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import type { DataStoreProxyProvider } from 'n8n-workflow';
|
||||
import type { DataTableProxyProvider } from 'n8n-workflow';
|
||||
|
||||
import type { ExecutionLifecycleHooks } from './execution-lifecycle-hooks';
|
||||
import type { ExternalSecretsProxy } from './external-secrets-proxy';
|
||||
|
|
@ -7,7 +7,7 @@ declare module 'n8n-workflow' {
|
|||
interface IWorkflowExecuteAdditionalData {
|
||||
hooks?: ExecutionLifecycleHooks;
|
||||
externalSecretsProxy: ExternalSecretsProxy;
|
||||
'data-table'?: { dataStoreProxyProvider: DataStoreProxyProvider };
|
||||
'data-table'?: { dataTableProxyProvider: DataTableProxyProvider };
|
||||
// Project ID is currently only added on the additionalData if the user
|
||||
// has data table listing permission for that project. We should consider
|
||||
// that only data tables belonging to their respective projects are shown.
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ import {
|
|||
} from './utils/binary-helper-functions';
|
||||
import { constructExecutionMetaData } from './utils/construct-execution-metadata';
|
||||
import { copyInputItems } from './utils/copy-input-items';
|
||||
import { getDataStoreHelperFunctions } from './utils/data-store-helper-functions';
|
||||
import { getDataTableHelperFunctions } from './utils/data-table-helper-functions';
|
||||
import { getDeduplicationHelperFunctions } from './utils/deduplication-helper-functions';
|
||||
import { getFileSystemHelperFunctions } from './utils/file-system-helper-functions';
|
||||
import { getInputConnectionData } from './utils/get-input-connection-data';
|
||||
|
|
@ -95,7 +95,7 @@ export class ExecuteContext extends BaseExecuteContext implements IExecuteFuncti
|
|||
connectionInputData,
|
||||
),
|
||||
...getBinaryHelperFunctions(additionalData, workflow.id),
|
||||
...getDataStoreHelperFunctions(additionalData, workflow, node),
|
||||
...getDataTableHelperFunctions(additionalData, workflow, node),
|
||||
...getSSHTunnelFunctions(),
|
||||
...getFileSystemHelperFunctions(node),
|
||||
...getDeduplicationHelperFunctions(workflow, node),
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import type {
|
|||
} from 'n8n-workflow';
|
||||
|
||||
import { NodeExecutionContext } from './node-execution-context';
|
||||
import { getDataStoreHelperFunctions } from './utils/data-store-helper-functions';
|
||||
import { getDataTableHelperFunctions } from './utils/data-table-helper-functions';
|
||||
import { extractValue } from './utils/extract-value';
|
||||
import { getRequestHelperFunctions } from './utils/request-helper-functions';
|
||||
import { getSSHTunnelFunctions } from './utils/ssh-tunnel-helper-functions';
|
||||
|
|
@ -29,7 +29,7 @@ export class LoadOptionsContext extends NodeExecutionContext implements ILoadOpt
|
|||
this.helpers = {
|
||||
...getSSHTunnelFunctions(),
|
||||
...getRequestHelperFunctions(workflow, node, additionalData),
|
||||
...getDataStoreHelperFunctions(additionalData, workflow, node),
|
||||
...getDataTableHelperFunctions(additionalData, workflow, node),
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ import {
|
|||
} from './utils/binary-helper-functions';
|
||||
import { constructExecutionMetaData } from './utils/construct-execution-metadata';
|
||||
import { copyInputItems } from './utils/copy-input-items';
|
||||
import { getDataStoreHelperFunctions } from './utils/data-store-helper-functions';
|
||||
import { getDataTableHelperFunctions } from './utils/data-table-helper-functions';
|
||||
import { getDeduplicationHelperFunctions } from './utils/deduplication-helper-functions';
|
||||
import { getFileSystemHelperFunctions } from './utils/file-system-helper-functions';
|
||||
// eslint-disable-next-line import-x/no-cycle
|
||||
|
|
@ -89,7 +89,7 @@ export class SupplyDataContext extends BaseExecuteContext implements ISupplyData
|
|||
...getSSHTunnelFunctions(),
|
||||
...getFileSystemHelperFunctions(node),
|
||||
...getBinaryHelperFunctions(additionalData, workflow.id),
|
||||
...getDataStoreHelperFunctions(additionalData, workflow, node),
|
||||
...getDataTableHelperFunctions(additionalData, workflow, node),
|
||||
...getDeduplicationHelperFunctions(workflow, node),
|
||||
assertBinaryData: (itemIndex, propertyName) =>
|
||||
assertBinaryData(inputData, node, itemIndex, propertyName, 0),
|
||||
|
|
|
|||
|
|
@ -1,30 +0,0 @@
|
|||
import type {
|
||||
DataStoreProxyFunctions,
|
||||
INode,
|
||||
Workflow,
|
||||
IWorkflowExecuteAdditionalData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export function getDataStoreHelperFunctions(
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
workflow: Workflow,
|
||||
node: INode,
|
||||
): Partial<DataStoreProxyFunctions> {
|
||||
const dataStoreProxyProvider = additionalData['data-table']?.dataStoreProxyProvider;
|
||||
if (!dataStoreProxyProvider) return {};
|
||||
return {
|
||||
getDataStoreAggregateProxy: async () =>
|
||||
await dataStoreProxyProvider.getDataStoreAggregateProxy(
|
||||
workflow,
|
||||
node,
|
||||
additionalData.dataTableProjectId,
|
||||
),
|
||||
getDataStoreProxy: async (dataStoreId: string) =>
|
||||
await dataStoreProxyProvider.getDataStoreProxy(
|
||||
workflow,
|
||||
node,
|
||||
dataStoreId,
|
||||
additionalData.dataTableProjectId,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
import type {
|
||||
DataTableProxyFunctions,
|
||||
INode,
|
||||
Workflow,
|
||||
IWorkflowExecuteAdditionalData,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
export function getDataTableHelperFunctions(
|
||||
additionalData: IWorkflowExecuteAdditionalData,
|
||||
workflow: Workflow,
|
||||
node: INode,
|
||||
): Partial<DataTableProxyFunctions> {
|
||||
const dataTableProxyProvider = additionalData['data-table']?.dataTableProxyProvider;
|
||||
if (!dataTableProxyProvider) return {};
|
||||
return {
|
||||
getDataTableAggregateProxy: async () =>
|
||||
await dataTableProxyProvider.getDataTableAggregateProxy(
|
||||
workflow,
|
||||
node,
|
||||
additionalData.dataTableProjectId,
|
||||
),
|
||||
getDataTableProxy: async (dataTableId: string) =>
|
||||
await dataTableProxyProvider.getDataTableProxy(
|
||||
workflow,
|
||||
node,
|
||||
dataTableId,
|
||||
additionalData.dataTableProjectId,
|
||||
),
|
||||
};
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
import { DATA_STORE_COLUMN_REGEX } from '@n8n/api-types';
|
||||
import { DATA_TABLE_COLUMN_REGEX } from '@n8n/api-types';
|
||||
|
||||
// Route and view identifiers
|
||||
export const DATA_STORE_VIEW = 'data-stores';
|
||||
|
|
@ -29,7 +29,7 @@ export const DEFAULT_ID_COLUMN_NAME = 'id';
|
|||
|
||||
export const MAX_COLUMN_NAME_LENGTH = 128;
|
||||
|
||||
export const COLUMN_NAME_REGEX = DATA_STORE_COLUMN_REGEX;
|
||||
export const COLUMN_NAME_REGEX = DATA_TABLE_COLUMN_REGEX;
|
||||
|
||||
export const MIN_LOADING_TIME = 500; // ms
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"alias": ["data", "table", "knowledge", "data store", "store", "sheet"],
|
||||
"alias": ["data", "table", "knowledge", "data table", "table", "sheet"],
|
||||
"subcategories": {
|
||||
"Core Nodes": ["Helpers"]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import type {
|
||||
IDataStoreProjectService,
|
||||
IDataTableProjectService,
|
||||
IDisplayOptions,
|
||||
IExecuteFunctions,
|
||||
INodeExecutionData,
|
||||
|
|
@ -65,7 +65,7 @@ export async function execute(
|
|||
|
||||
export async function executeBulk(
|
||||
this: IExecuteFunctions,
|
||||
proxy: IDataStoreProjectService,
|
||||
proxy: IDataTableProjectService,
|
||||
): Promise<INodeExecutionData[]> {
|
||||
const optimizeBulkEnabled = this.getNodeParameter('options.optimizeBulk', 0, false);
|
||||
const rows = this.getInputData().flatMap((_, i) => [getAddRow(this, i)]);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import type { DataStoreColumnJsType } from 'n8n-workflow';
|
||||
import type { DataTableColumnJsType } from 'n8n-workflow';
|
||||
|
||||
export const ANY_CONDITION = 'anyCondition';
|
||||
export const ALL_CONDITIONS = 'allConditions';
|
||||
|
|
@ -15,5 +15,5 @@ export type FieldEntry =
|
|||
| {
|
||||
keyName: string;
|
||||
condition?: 'eq' | 'neq' | 'like' | 'ilike' | 'gt' | 'gte' | 'lt' | 'lte';
|
||||
keyValue: DataStoreColumnJsType;
|
||||
keyValue: DataTableColumnJsType;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import { DATA_TABLE_SYSTEM_COLUMNS, NodeOperationError } from 'n8n-workflow';
|
||||
import type {
|
||||
DataTableFilter,
|
||||
DataStoreRowReturn,
|
||||
IDataStoreProjectService,
|
||||
DataTableRowReturn,
|
||||
IDataTableProjectService,
|
||||
IDisplayOptions,
|
||||
IExecuteFunctions,
|
||||
INodeProperties,
|
||||
|
|
@ -144,10 +144,10 @@ export async function getSelectFilter(
|
|||
export async function executeSelectMany(
|
||||
ctx: IExecuteFunctions,
|
||||
index: number,
|
||||
dataStoreProxy: IDataStoreProjectService,
|
||||
dataStoreProxy: IDataTableProjectService,
|
||||
rejectEmpty = false,
|
||||
limit?: number,
|
||||
): Promise<Array<{ json: DataStoreRowReturn }>> {
|
||||
): Promise<Array<{ json: DataTableRowReturn }>> {
|
||||
const filter = await getSelectFilter(ctx, index);
|
||||
|
||||
if (rejectEmpty && filter.filters.length === 0) {
|
||||
|
|
@ -155,7 +155,7 @@ export async function executeSelectMany(
|
|||
}
|
||||
|
||||
const PAGE_SIZE = 1000;
|
||||
const result: Array<{ json: DataStoreRowReturn }> = [];
|
||||
const result: Array<{ json: DataTableRowReturn }> = [];
|
||||
|
||||
const returnAll = ctx.getNodeParameter('returnAll', index, false);
|
||||
limit = limit ?? (!returnAll ? ctx.getNodeParameter('limit', index, ROWS_LIMIT_DEFAULT) : 0);
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ import type {
|
|||
IDataObject,
|
||||
INode,
|
||||
DataTableFilter,
|
||||
IDataStoreProjectAggregateService,
|
||||
IDataStoreProjectService,
|
||||
IDataTableProjectAggregateService,
|
||||
IDataTableProjectService,
|
||||
IExecuteFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
DataStoreColumnJsType,
|
||||
DataTableColumnJsType,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeOperationError } from 'n8n-workflow';
|
||||
|
||||
|
|
@ -28,50 +28,50 @@ function isDateLike(v: unknown): v is DateLike {
|
|||
export async function getDataTableProxyExecute(
|
||||
ctx: IExecuteFunctions,
|
||||
index: number = 0,
|
||||
): Promise<IDataStoreProjectService> {
|
||||
if (ctx.helpers.getDataStoreProxy === undefined)
|
||||
): Promise<IDataTableProjectService> {
|
||||
if (ctx.helpers.getDataTableProxy === undefined)
|
||||
throw new NodeOperationError(
|
||||
ctx.getNode(),
|
||||
'Attempted to use Data table node but the module is disabled',
|
||||
);
|
||||
|
||||
const dataStoreId = ctx.getNodeParameter(DATA_TABLE_ID_FIELD, index, undefined, {
|
||||
const dataTableId = ctx.getNodeParameter(DATA_TABLE_ID_FIELD, index, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
return await ctx.helpers.getDataStoreProxy(dataStoreId);
|
||||
return await ctx.helpers.getDataTableProxy(dataTableId);
|
||||
}
|
||||
|
||||
export async function getDataTableProxyLoadOptions(
|
||||
ctx: ILoadOptionsFunctions,
|
||||
): Promise<IDataStoreProjectService | undefined> {
|
||||
if (ctx.helpers.getDataStoreProxy === undefined)
|
||||
): Promise<IDataTableProjectService | undefined> {
|
||||
if (ctx.helpers.getDataTableProxy === undefined)
|
||||
throw new NodeOperationError(
|
||||
ctx.getNode(),
|
||||
'Attempted to use Data table node but the module is disabled',
|
||||
);
|
||||
|
||||
const dataStoreId = ctx.getNodeParameter(DATA_TABLE_ID_FIELD, undefined, {
|
||||
const dataTableId = ctx.getNodeParameter(DATA_TABLE_ID_FIELD, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
|
||||
if (!dataStoreId) {
|
||||
if (!dataTableId) {
|
||||
return;
|
||||
}
|
||||
|
||||
return await ctx.helpers.getDataStoreProxy(dataStoreId);
|
||||
return await ctx.helpers.getDataTableProxy(dataTableId);
|
||||
}
|
||||
|
||||
export async function getDataTableAggregateProxy(
|
||||
ctx: IExecuteFunctions | ILoadOptionsFunctions,
|
||||
): Promise<IDataStoreProjectAggregateService> {
|
||||
if (ctx.helpers.getDataStoreAggregateProxy === undefined)
|
||||
): Promise<IDataTableProjectAggregateService> {
|
||||
if (ctx.helpers.getDataTableAggregateProxy === undefined)
|
||||
throw new NodeOperationError(
|
||||
ctx.getNode(),
|
||||
'Attempted to use Data table node but the module is disabled',
|
||||
);
|
||||
|
||||
return await ctx.helpers.getDataStoreAggregateProxy();
|
||||
return await ctx.helpers.getDataTableAggregateProxy();
|
||||
}
|
||||
|
||||
export function isFieldEntry(obj: unknown): obj is FieldEntry {
|
||||
|
|
@ -134,9 +134,9 @@ export function dataObjectToApiInput(
|
|||
data: IDataObject,
|
||||
node: INode,
|
||||
row: number,
|
||||
): Record<string, DataStoreColumnJsType> {
|
||||
): Record<string, DataTableColumnJsType> {
|
||||
return Object.fromEntries(
|
||||
Object.entries(data).map(([k, v]): [string, DataStoreColumnJsType] => {
|
||||
Object.entries(data).map(([k, v]): [string, DataTableColumnJsType] => {
|
||||
if (v === undefined || v === null) return [k, null];
|
||||
|
||||
if (Array.isArray(v)) {
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import {
|
||||
type INode,
|
||||
NodeOperationError,
|
||||
type IDataStoreProjectService,
|
||||
type IDataTableProjectService,
|
||||
type IExecuteFunctions,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
|
|
@ -13,9 +13,9 @@ import { executeSelectMany, getSelectFilter } from '../../common/selectMany';
|
|||
describe('selectMany utils', () => {
|
||||
let mockExecuteFunctions: IExecuteFunctions;
|
||||
const getManyRowsAndCount = jest.fn();
|
||||
const dataStoreProxy = jest.mocked<IDataStoreProjectService>({
|
||||
const dataStoreProxy = jest.mocked<IDataTableProjectService>({
|
||||
getManyRowsAndCount,
|
||||
} as unknown as IDataStoreProjectService);
|
||||
} as unknown as IDataTableProjectService);
|
||||
const dataTableId = 2345;
|
||||
let filters: FieldEntry[];
|
||||
const node = { id: 1 } as unknown as INode;
|
||||
|
|
@ -29,7 +29,7 @@ describe('selectMany utils', () => {
|
|||
},
|
||||
];
|
||||
|
||||
const mockDataStoreProxy = {
|
||||
const mockDataTableProxy = {
|
||||
getColumns: jest.fn().mockResolvedValue([
|
||||
{ name: 'name', type: 'string' },
|
||||
{ name: 'age', type: 'number' },
|
||||
|
|
@ -50,7 +50,7 @@ describe('selectMany utils', () => {
|
|||
}
|
||||
}),
|
||||
helpers: {
|
||||
getDataStoreProxy: jest.fn().mockResolvedValue(mockDataStoreProxy),
|
||||
getDataTableProxy: jest.fn().mockResolvedValue(mockDataTableProxy),
|
||||
},
|
||||
} as unknown as IExecuteFunctions;
|
||||
|
||||
|
|
|
|||
|
|
@ -190,7 +190,7 @@ export class EvaluationTrigger implements INodeType {
|
|||
? (this.getNodeParameter('maxRows', 0, MAX_ROWS) as number)
|
||||
: MAX_ROWS;
|
||||
|
||||
if (this.helpers.getDataStoreProxy === undefined) {
|
||||
if (this.helpers.getDataTableProxy === undefined) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
'Attempted to use Data table node but the module is disabled',
|
||||
|
|
@ -205,7 +205,7 @@ export class EvaluationTrigger implements INodeType {
|
|||
const dataTableId = this.getNodeParameter('dataTableId', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
const dataTableProxy = await this.helpers.getDataStoreProxy(dataTableId);
|
||||
const dataTableProxy = await this.helpers.getDataTableProxy(dataTableId);
|
||||
|
||||
const filter = await getDataTableFilter(this, 0);
|
||||
|
||||
|
|
@ -318,7 +318,7 @@ export class EvaluationTrigger implements INodeType {
|
|||
? (this.getNodeParameter('maxRows', 0, MAX_ROWS) as number)
|
||||
: MAX_ROWS;
|
||||
|
||||
if (this.helpers.getDataStoreProxy === undefined) {
|
||||
if (this.helpers.getDataTableProxy === undefined) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
'Attempted to use Data table node but the module is disabled',
|
||||
|
|
@ -328,7 +328,7 @@ export class EvaluationTrigger implements INodeType {
|
|||
const dataTableId = this.getNodeParameter('dataTableId', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
const dataTableProxy = await this.helpers.getDataStoreProxy(dataTableId);
|
||||
const dataTableProxy = await this.helpers.getDataTableProxy(dataTableId);
|
||||
|
||||
const filter = await getDataTableFilter(this, 0);
|
||||
const { data } = await dataTableProxy.getManyRowsAndCount({
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { mock } from 'jest-mock-extended';
|
||||
import {
|
||||
type IDataStoreProjectService,
|
||||
type IDataTableProjectService,
|
||||
NodeOperationError,
|
||||
type AssignmentCollectionValue,
|
||||
type IExecuteFunctions,
|
||||
|
|
@ -15,14 +15,14 @@ describe('Test Evaluation', () => {
|
|||
const sheetName = 'Sheet5';
|
||||
const spreadsheetId = '1oqFpPgEPTGDw7BPkp1SfPXq3Cb3Hyr1SROtf-Ec4zvA';
|
||||
|
||||
const mockDataTable = mock<IDataStoreProjectService>({
|
||||
const mockDataTable = mock<IDataTableProjectService>({
|
||||
getColumns: jest.fn(),
|
||||
addColumn: jest.fn(),
|
||||
updateRows: jest.fn(),
|
||||
});
|
||||
|
||||
const mockExecuteFunctions = mock<IExecuteFunctions>({
|
||||
helpers: { getDataStoreProxy: jest.fn().mockResolvedValue(mockDataTable) },
|
||||
helpers: { getDataTableProxy: jest.fn().mockResolvedValue(mockDataTable) },
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
|
|
|
|||
|
|
@ -338,7 +338,7 @@ describe('Evaluation Trigger Node', () => {
|
|||
mockExecuteFunctions = mockDeep<IExecuteFunctions>({
|
||||
getNode: jest.fn().mockReturnValue({ typeVersion: 4.7 }),
|
||||
helpers: {
|
||||
getDataStoreProxy: jest.fn().mockResolvedValue(mockDataTable),
|
||||
getDataTableProxy: jest.fn().mockResolvedValue(mockDataTable),
|
||||
},
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@ describe('setOutputs', () => {
|
|||
addExecutionHints: jest.fn(),
|
||||
getMode: jest.fn().mockReturnValue('evaluation'),
|
||||
helpers: {
|
||||
getDataStoreProxy: jest.fn().mockResolvedValue(mockDataTable),
|
||||
getDataTableProxy: jest.fn().mockResolvedValue(mockDataTable),
|
||||
},
|
||||
...options,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ import type {
|
|||
INodeExecutionData,
|
||||
JsonObject,
|
||||
JsonValue,
|
||||
DataStoreColumnJsType,
|
||||
DataTableColumnJsType,
|
||||
} from 'n8n-workflow';
|
||||
|
||||
import { getGoogleSheet, getSheet } from './evaluationTriggerUtils';
|
||||
|
|
@ -52,7 +52,7 @@ function isOutputsArray(
|
|||
);
|
||||
}
|
||||
|
||||
export function toDataTableValue(value: JsonValue): DataStoreColumnJsType {
|
||||
export function toDataTableValue(value: JsonValue): DataTableColumnJsType {
|
||||
if (
|
||||
typeof value === 'string' ||
|
||||
typeof value === 'number' ||
|
||||
|
|
@ -139,7 +139,7 @@ export async function setOutputs(this: IExecuteFunctions): Promise<INodeExecutio
|
|||
const source = this.getNodeParameter('source', 0) as string;
|
||||
|
||||
if (source === 'dataTable') {
|
||||
if (this.helpers.getDataStoreProxy === undefined) {
|
||||
if (this.helpers.getDataTableProxy === undefined) {
|
||||
throw new NodeOperationError(
|
||||
this.getNode(),
|
||||
'Attempted to use Data table node but the module is disabled',
|
||||
|
|
@ -149,7 +149,7 @@ export async function setOutputs(this: IExecuteFunctions): Promise<INodeExecutio
|
|||
const dataTableId = this.getNodeParameter('dataTableId', 0, undefined, {
|
||||
extractValue: true,
|
||||
}) as string;
|
||||
const dataTableProxy = await this.helpers.getDataStoreProxy(dataTableId);
|
||||
const dataTableProxy = await this.helpers.getDataTableProxy(dataTableId);
|
||||
|
||||
const rowId = typeof evaluationTrigger.row_id === 'number' ? evaluationTrigger.row_id : 1;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,190 +0,0 @@
|
|||
export type DataStoreColumnType = 'string' | 'number' | 'boolean' | 'date';
|
||||
|
||||
export type DataStoreColumn = {
|
||||
id: string;
|
||||
name: string;
|
||||
type: DataStoreColumnType;
|
||||
index: number;
|
||||
dataTableId: string;
|
||||
};
|
||||
|
||||
export type DataStore = {
|
||||
id: string;
|
||||
name: string;
|
||||
columns: DataStoreColumn[];
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export type CreateDataStoreColumnOptions = Pick<DataStoreColumn, 'name' | 'type'> &
|
||||
Partial<Pick<DataStoreColumn, 'index'>>;
|
||||
|
||||
export type CreateDataStoreOptions = Pick<DataStore, 'name'> & {
|
||||
columns: CreateDataStoreColumnOptions[];
|
||||
};
|
||||
|
||||
export type UpdateDataStoreOptions = { name: string };
|
||||
|
||||
export type ListDataStoreOptions = {
|
||||
filter?: Record<string, string | string[]>;
|
||||
sortBy?:
|
||||
| 'name:asc'
|
||||
| 'name:desc'
|
||||
| 'createdAt:asc'
|
||||
| 'createdAt:desc'
|
||||
| 'updatedAt:asc'
|
||||
| 'updatedAt:desc'
|
||||
| 'sizeBytes:asc'
|
||||
| 'sizeBytes:desc';
|
||||
take?: number;
|
||||
skip?: number;
|
||||
};
|
||||
|
||||
export type DataTableFilter = {
|
||||
type: 'and' | 'or';
|
||||
filters: Array<{
|
||||
columnName: string;
|
||||
condition: 'eq' | 'neq' | 'like' | 'ilike' | 'gt' | 'gte' | 'lt' | 'lte';
|
||||
value: DataStoreColumnJsType;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type ListDataStoreRowsOptions = {
|
||||
filter?: DataTableFilter;
|
||||
sortBy?: [string, 'ASC' | 'DESC'];
|
||||
take?: number;
|
||||
skip?: number;
|
||||
};
|
||||
|
||||
export type UpdateDataStoreRowOptions = {
|
||||
filter: DataTableFilter;
|
||||
data: DataStoreRow;
|
||||
dryRun?: boolean;
|
||||
};
|
||||
|
||||
export type UpsertDataStoreRowOptions = {
|
||||
filter: DataTableFilter;
|
||||
data: DataStoreRow;
|
||||
dryRun?: boolean;
|
||||
};
|
||||
|
||||
export type DeleteDataTableRowsOptions = {
|
||||
filter: DataTableFilter;
|
||||
dryRun?: boolean;
|
||||
};
|
||||
|
||||
export type MoveDataStoreColumnOptions = {
|
||||
targetIndex: number;
|
||||
};
|
||||
|
||||
export type AddDataStoreColumnOptions = Pick<DataStoreColumn, 'name' | 'type'> &
|
||||
Partial<Pick<DataStoreColumn, 'index'>>;
|
||||
|
||||
export type DataStoreColumnJsType = string | number | boolean | Date | null;
|
||||
|
||||
export const DATA_TABLE_SYSTEM_COLUMN_TYPE_MAP: Record<string, DataStoreColumnType> = {
|
||||
id: 'number',
|
||||
createdAt: 'date',
|
||||
updatedAt: 'date',
|
||||
};
|
||||
|
||||
export const DATA_TABLE_SYSTEM_COLUMNS = Object.keys(DATA_TABLE_SYSTEM_COLUMN_TYPE_MAP);
|
||||
export const DATA_TABLE_SYSTEM_TESTING_COLUMN = 'dryRunState';
|
||||
|
||||
export type DataStoreRowReturnBase = {
|
||||
id: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
export type DataStoreRow = Record<string, DataStoreColumnJsType>;
|
||||
export type DataStoreRows = DataStoreRow[];
|
||||
export type DataStoreRowReturn = DataStoreRow & DataStoreRowReturnBase;
|
||||
export type DataStoreRowsReturn = DataStoreRowReturn[];
|
||||
|
||||
export type DataStoreRowReturnWithState = DataStoreRow & {
|
||||
id: number | null;
|
||||
createdAt: Date | null;
|
||||
updatedAt: Date | null;
|
||||
dryRunState: 'before' | 'after';
|
||||
};
|
||||
|
||||
export type DataStoreRowUpdatePair = {
|
||||
before: DataStoreRowReturn;
|
||||
after: DataStoreRowReturn;
|
||||
};
|
||||
|
||||
export type DataTableInsertRowsReturnType = 'all' | 'id' | 'count';
|
||||
export type DataTableInsertRowsBulkResult = { success: true; insertedRows: number };
|
||||
export type DataTableInsertRowsResult<
|
||||
T extends DataTableInsertRowsReturnType = DataTableInsertRowsReturnType,
|
||||
> = T extends 'all'
|
||||
? DataStoreRowReturn[]
|
||||
: T extends 'id'
|
||||
? Array<Pick<DataStoreRowReturn, 'id'>>
|
||||
: DataTableInsertRowsBulkResult;
|
||||
|
||||
export type DataTableSizeStatus = 'ok' | 'warn' | 'error';
|
||||
|
||||
export type DataTableInfo = {
|
||||
id: string;
|
||||
name: string;
|
||||
projectId: string;
|
||||
projectName: string;
|
||||
sizeBytes: number;
|
||||
};
|
||||
|
||||
export type DataTableInfoById = Record<string, DataTableInfo>;
|
||||
|
||||
export type DataTablesSizeData = {
|
||||
totalBytes: number;
|
||||
dataTables: DataTableInfoById;
|
||||
};
|
||||
|
||||
export type DataTablesSizeResult = DataTablesSizeData & {
|
||||
quotaStatus: DataTableSizeStatus;
|
||||
};
|
||||
|
||||
// APIs for a data store service operating on a specific projectId
|
||||
export interface IDataStoreProjectAggregateService {
|
||||
getProjectId(): string;
|
||||
|
||||
createDataStore(options: CreateDataStoreOptions): Promise<DataStore>;
|
||||
|
||||
getManyAndCount(options: ListDataStoreOptions): Promise<{ count: number; data: DataStore[] }>;
|
||||
|
||||
deleteDataStoreAll(): Promise<boolean>;
|
||||
}
|
||||
// APIs for a data store service operating on a specific projectId and dataStoreId
|
||||
export interface IDataStoreProjectService {
|
||||
updateDataStore(options: UpdateDataStoreOptions): Promise<boolean>;
|
||||
|
||||
deleteDataStore(): Promise<boolean>;
|
||||
|
||||
getColumns(): Promise<DataStoreColumn[]>;
|
||||
|
||||
addColumn(options: AddDataStoreColumnOptions): Promise<DataStoreColumn>;
|
||||
|
||||
moveColumn(columnId: string, options: MoveDataStoreColumnOptions): Promise<boolean>;
|
||||
|
||||
deleteColumn(columnId: string): Promise<boolean>;
|
||||
|
||||
getManyRowsAndCount(
|
||||
dto: Partial<ListDataStoreRowsOptions>,
|
||||
): Promise<{ count: number; data: DataStoreRowsReturn }>;
|
||||
|
||||
insertRows<T extends DataTableInsertRowsReturnType>(
|
||||
rows: DataStoreRows,
|
||||
returnType: T,
|
||||
): Promise<DataTableInsertRowsResult<T>>;
|
||||
|
||||
updateRows(
|
||||
options: UpdateDataStoreRowOptions,
|
||||
): Promise<DataStoreRowReturn[] | DataStoreRowReturnWithState[]>;
|
||||
|
||||
upsertRow(
|
||||
options: UpsertDataStoreRowOptions,
|
||||
): Promise<DataStoreRowReturn[] | DataStoreRowReturnWithState[]>;
|
||||
|
||||
deleteRows(options: DeleteDataTableRowsOptions): Promise<DataStoreRowReturn[]>;
|
||||
}
|
||||
190
packages/workflow/src/data-table.types.ts
Normal file
190
packages/workflow/src/data-table.types.ts
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
export type DataTableColumnType = 'string' | 'number' | 'boolean' | 'date';
|
||||
|
||||
export type DataTableColumn = {
|
||||
id: string;
|
||||
name: string;
|
||||
type: DataTableColumnType;
|
||||
index: number;
|
||||
dataTableId: string;
|
||||
};
|
||||
|
||||
export type DataTable = {
|
||||
id: string;
|
||||
name: string;
|
||||
columns: DataTableColumn[];
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
projectId: string;
|
||||
};
|
||||
|
||||
export type CreateDataTableColumnOptions = Pick<DataTableColumn, 'name' | 'type'> &
|
||||
Partial<Pick<DataTableColumn, 'index'>>;
|
||||
|
||||
export type CreateDataTableOptions = Pick<DataTable, 'name'> & {
|
||||
columns: CreateDataTableColumnOptions[];
|
||||
};
|
||||
|
||||
export type UpdateDataTableOptions = { name: string };
|
||||
|
||||
export type ListDataTableOptions = {
|
||||
filter?: Record<string, string | string[]>;
|
||||
sortBy?:
|
||||
| 'name:asc'
|
||||
| 'name:desc'
|
||||
| 'createdAt:asc'
|
||||
| 'createdAt:desc'
|
||||
| 'updatedAt:asc'
|
||||
| 'updatedAt:desc'
|
||||
| 'sizeBytes:asc'
|
||||
| 'sizeBytes:desc';
|
||||
take?: number;
|
||||
skip?: number;
|
||||
};
|
||||
|
||||
export type DataTableFilter = {
|
||||
type: 'and' | 'or';
|
||||
filters: Array<{
|
||||
columnName: string;
|
||||
condition: 'eq' | 'neq' | 'like' | 'ilike' | 'gt' | 'gte' | 'lt' | 'lte';
|
||||
value: DataTableColumnJsType;
|
||||
}>;
|
||||
};
|
||||
|
||||
export type ListDataTableRowsOptions = {
|
||||
filter?: DataTableFilter;
|
||||
sortBy?: [string, 'ASC' | 'DESC'];
|
||||
take?: number;
|
||||
skip?: number;
|
||||
};
|
||||
|
||||
export type UpdateDataTableRowOptions = {
|
||||
filter: DataTableFilter;
|
||||
data: DataTableRow;
|
||||
dryRun?: boolean;
|
||||
};
|
||||
|
||||
export type UpsertDataTableRowOptions = {
|
||||
filter: DataTableFilter;
|
||||
data: DataTableRow;
|
||||
dryRun?: boolean;
|
||||
};
|
||||
|
||||
export type DeleteDataTableRowsOptions = {
|
||||
filter: DataTableFilter;
|
||||
dryRun?: boolean;
|
||||
};
|
||||
|
||||
export type MoveDataTableColumnOptions = {
|
||||
targetIndex: number;
|
||||
};
|
||||
|
||||
export type AddDataTableColumnOptions = Pick<DataTableColumn, 'name' | 'type'> &
|
||||
Partial<Pick<DataTableColumn, 'index'>>;
|
||||
|
||||
export type DataTableColumnJsType = string | number | boolean | Date | null;
|
||||
|
||||
export const DATA_TABLE_SYSTEM_COLUMN_TYPE_MAP: Record<string, DataTableColumnType> = {
|
||||
id: 'number',
|
||||
createdAt: 'date',
|
||||
updatedAt: 'date',
|
||||
};
|
||||
|
||||
export const DATA_TABLE_SYSTEM_COLUMNS = Object.keys(DATA_TABLE_SYSTEM_COLUMN_TYPE_MAP);
|
||||
export const DATA_TABLE_SYSTEM_TESTING_COLUMN = 'dryRunState';
|
||||
|
||||
export type DataTableRowReturnBase = {
|
||||
id: number;
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
};
|
||||
export type DataTableRow = Record<string, DataTableColumnJsType>;
|
||||
export type DataTableRows = DataTableRow[];
|
||||
export type DataTableRowReturn = DataTableRow & DataTableRowReturnBase;
|
||||
export type DataTableRowsReturn = DataTableRowReturn[];
|
||||
|
||||
export type DataTableRowReturnWithState = DataTableRow & {
|
||||
id: number | null;
|
||||
createdAt: Date | null;
|
||||
updatedAt: Date | null;
|
||||
dryRunState: 'before' | 'after';
|
||||
};
|
||||
|
||||
export type DataTableRowUpdatePair = {
|
||||
before: DataTableRowReturn;
|
||||
after: DataTableRowReturn;
|
||||
};
|
||||
|
||||
export type DataTableInsertRowsReturnType = 'all' | 'id' | 'count';
|
||||
export type DataTableInsertRowsBulkResult = { success: true; insertedRows: number };
|
||||
export type DataTableInsertRowsResult<
|
||||
T extends DataTableInsertRowsReturnType = DataTableInsertRowsReturnType,
|
||||
> = T extends 'all'
|
||||
? DataTableRowReturn[]
|
||||
: T extends 'id'
|
||||
? Array<Pick<DataTableRowReturn, 'id'>>
|
||||
: DataTableInsertRowsBulkResult;
|
||||
|
||||
export type DataTableSizeStatus = 'ok' | 'warn' | 'error';
|
||||
|
||||
export type DataTableInfo = {
|
||||
id: string;
|
||||
name: string;
|
||||
projectId: string;
|
||||
projectName: string;
|
||||
sizeBytes: number;
|
||||
};
|
||||
|
||||
export type DataTableInfoById = Record<string, DataTableInfo>;
|
||||
|
||||
export type DataTablesSizeData = {
|
||||
totalBytes: number;
|
||||
dataTables: DataTableInfoById;
|
||||
};
|
||||
|
||||
export type DataTablesSizeResult = DataTablesSizeData & {
|
||||
quotaStatus: DataTableSizeStatus;
|
||||
};
|
||||
|
||||
// APIs for a data table service operating on a specific projectId
|
||||
export interface IDataTableProjectAggregateService {
|
||||
getProjectId(): string;
|
||||
|
||||
createDataTable(options: CreateDataTableOptions): Promise<DataTable>;
|
||||
|
||||
getManyAndCount(options: ListDataTableOptions): Promise<{ count: number; data: DataTable[] }>;
|
||||
|
||||
deleteDataTableAll(): Promise<boolean>;
|
||||
}
|
||||
// APIs for a data table service operating on a specific projectId and dataTableId
|
||||
export interface IDataTableProjectService {
|
||||
updateDataTable(options: UpdateDataTableOptions): Promise<boolean>;
|
||||
|
||||
deleteDataTable(): Promise<boolean>;
|
||||
|
||||
getColumns(): Promise<DataTableColumn[]>;
|
||||
|
||||
addColumn(options: AddDataTableColumnOptions): Promise<DataTableColumn>;
|
||||
|
||||
moveColumn(columnId: string, options: MoveDataTableColumnOptions): Promise<boolean>;
|
||||
|
||||
deleteColumn(columnId: string): Promise<boolean>;
|
||||
|
||||
getManyRowsAndCount(
|
||||
dto: Partial<ListDataTableRowsOptions>,
|
||||
): Promise<{ count: number; data: DataTableRowsReturn }>;
|
||||
|
||||
insertRows<T extends DataTableInsertRowsReturnType>(
|
||||
rows: DataTableRows,
|
||||
returnType: T,
|
||||
): Promise<DataTableInsertRowsResult<T>>;
|
||||
|
||||
updateRows(
|
||||
options: UpdateDataTableRowOptions,
|
||||
): Promise<DataTableRowReturn[] | DataTableRowReturnWithState[]>;
|
||||
|
||||
upsertRow(
|
||||
options: UpsertDataTableRowOptions,
|
||||
): Promise<DataTableRowReturn[] | DataTableRowReturnWithState[]>;
|
||||
|
||||
deleteRows(options: DeleteDataTableRowsOptions): Promise<DataTableRowReturn[]>;
|
||||
}
|
||||
|
|
@ -7,7 +7,7 @@ export * from './errors';
|
|||
export * from './constants';
|
||||
export * from './common';
|
||||
export * from './cron';
|
||||
export * from './data-store.types';
|
||||
export * from './data-table.types';
|
||||
export * from './deferred-promise';
|
||||
export * from './global-state';
|
||||
export * from './interfaces';
|
||||
|
|
|
|||
|
|
@ -14,9 +14,9 @@ import type { URLSearchParams } from 'url';
|
|||
|
||||
import type { CODE_EXECUTION_MODES, CODE_LANGUAGES, LOG_LEVELS } from './constants';
|
||||
import type {
|
||||
IDataStoreProjectAggregateService,
|
||||
IDataStoreProjectService,
|
||||
} from './data-store.types';
|
||||
IDataTableProjectAggregateService,
|
||||
IDataTableProjectService,
|
||||
} from './data-table.types';
|
||||
import type { IDeferredPromise } from './deferred-promise';
|
||||
import type { ExecutionCancelledError } from './errors';
|
||||
import type { ExpressionError } from './errors/expression.error';
|
||||
|
|
@ -925,24 +925,24 @@ type FunctionsBaseWithRequiredKeys<Keys extends keyof FunctionsBase> = Functions
|
|||
|
||||
export type ContextType = 'flow' | 'node';
|
||||
|
||||
export type DataStoreProxyProvider = {
|
||||
getDataStoreAggregateProxy(
|
||||
export type DataTableProxyProvider = {
|
||||
getDataTableAggregateProxy(
|
||||
workflow: Workflow,
|
||||
node: INode,
|
||||
projectId?: string,
|
||||
): Promise<IDataStoreProjectAggregateService>;
|
||||
getDataStoreProxy(
|
||||
): Promise<IDataTableProjectAggregateService>;
|
||||
getDataTableProxy(
|
||||
workflow: Workflow,
|
||||
node: INode,
|
||||
dataStoreId: string,
|
||||
dataTableId: string,
|
||||
projectId?: string,
|
||||
): Promise<IDataStoreProjectService>;
|
||||
): Promise<IDataTableProjectService>;
|
||||
};
|
||||
|
||||
export type DataStoreProxyFunctions = {
|
||||
// These are optional to account for situations where the data-store module is disabled
|
||||
getDataStoreAggregateProxy?(): Promise<IDataStoreProjectAggregateService>;
|
||||
getDataStoreProxy?(dataStoreId: string): Promise<IDataStoreProjectService>;
|
||||
export type DataTableProxyFunctions = {
|
||||
// These are optional to account for situations where the data-table module is disabled
|
||||
getDataTableAggregateProxy?(): Promise<IDataTableProjectAggregateService>;
|
||||
getDataTableProxy?(dataTableId: string): Promise<IDataTableProjectService>;
|
||||
};
|
||||
|
||||
type BaseExecutionFunctions = FunctionsBaseWithRequiredKeys<'getMode'> & {
|
||||
|
|
@ -1008,7 +1008,7 @@ export type IExecuteFunctions = ExecuteFunctions.GetNodeParameterFn &
|
|||
DeduplicationHelperFunctions &
|
||||
FileSystemHelperFunctions &
|
||||
SSHTunnelFunctions &
|
||||
DataStoreProxyFunctions & {
|
||||
DataTableProxyFunctions & {
|
||||
normalizeItems(items: INodeExecutionData | INodeExecutionData[]): INodeExecutionData[];
|
||||
constructExecutionMetaData(
|
||||
inputData: INodeExecutionData[],
|
||||
|
|
@ -1093,7 +1093,7 @@ export interface ILoadOptionsFunctions extends FunctionsBase {
|
|||
): NodeParameterValueType | object | undefined;
|
||||
getCurrentNodeParameters(): INodeParameters | undefined;
|
||||
|
||||
helpers: RequestHelperFunctions & SSHTunnelFunctions & DataStoreProxyFunctions;
|
||||
helpers: RequestHelperFunctions & SSHTunnelFunctions & DataTableProxyFunctions;
|
||||
}
|
||||
|
||||
export type FieldValueOption = { name: string; type: FieldType | 'any' };
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user