From 461faf872afd862f3031ca056e9c98f5920320cd Mon Sep 17 00:00:00 2001 From: Elias Kassell Date: Sun, 7 Jul 2024 16:56:52 +0200 Subject: [PATCH] Incremental tables as protos, via a new class (#1773) * Incremental tables as protos, via a new class * Fix integration test --- core/BUILD | 1 + core/actions/incremental_table.ts | 592 ++++++++++++++++++++++++++++++ core/actions/index.ts | 10 +- core/actions/view.ts | 5 +- core/main.ts | 4 +- core/main_test.ts | 2 + core/session.ts | 23 +- core/utils.ts | 3 +- 8 files changed, 630 insertions(+), 10 deletions(-) create mode 100644 core/actions/incremental_table.ts diff --git a/core/BUILD b/core/BUILD index 6309cf481..504e07353 100644 --- a/core/BUILD +++ b/core/BUILD @@ -21,6 +21,7 @@ ts_library( "actions/assertion.ts", "actions/declaration.ts", "actions/view.ts", + "actions/incremental_table.ts", "actions/index.ts", "actions/operation.ts", "actions/notebook.ts", diff --git a/core/actions/incremental_table.ts b/core/actions/incremental_table.ts new file mode 100644 index 000000000..596e9d45a --- /dev/null +++ b/core/actions/incremental_table.ts @@ -0,0 +1,592 @@ +import { verifyObjectMatchesProto, VerifyProtoErrorBehaviour } from "df/common/protos"; +import { ActionBuilder } from "df/core/actions"; +import { Assertion } from "df/core/actions/assertion"; +import { ITableContext, Table } from "df/core/actions/table"; +import { ColumnDescriptors } from "df/core/column_descriptors"; +import { Contextable, Resolvable } from "df/core/common"; +import * as Path from "df/core/path"; +import { Session } from "df/core/session"; +import { + actionConfigToCompiledGraphTarget, + addDependenciesToActionDependencyTargets, + nativeRequire, + resolvableAsTarget, + resolveActionsConfigFilename, + setNameAndTarget, + toResolvable, + validateQueryString +} from "df/core/utils"; +import { dataform } from "df/protos/ts"; + +/** + * @hidden + * This maintains backwards compatability with older versions. + * TODO(ekrekr): consider breaking backwards compatability of these in v4. + */ +interface ILegacyIncrementalTableConfig extends dataform.ActionConfig.IncrementalTableConfig { + dependencies: Resolvable[]; + database: string; + schema: string; + fileName: string; + type: string; + bigquery?: ILegacyIncrementalTableBigqueryConfig; + // Legacy incremental table config's table assertions cannot directly extend the protobuf + // incremental table config definition because of legacy incremental table config's flexible + // types. + assertions: any; +} + +interface ILegacyIncrementalTableBigqueryConfig { + partitionBy?: string; + clusterBy?: string[]; + updatePartitionFilter?: string; + labels?: { [key: string]: string }; + partitionExpirationDays?: number; + requirePartitionFilter?: boolean; + additionalOptions?: { [key: string]: string }; +} + +/** + * @hidden + */ +export class IncrementalTable extends ActionBuilder { + // TODO(ekrekr): make this field private, to enforce proto update logic to happen in this class. + public proto: dataform.ITable = dataform.Table.create({ + type: "incremental", + enumType: dataform.TableType.INCREMENTAL, + disabled: false, + tags: [] + }); + + // Hold a reference to the Session instance. + public session: Session; + + // If true, adds the inline assertions of dependencies as direct dependencies for this action. + public dependOnDependencyAssertions: boolean = false; + + // We delay contextification until the final compile step, so hold these here for now. + public contextableQuery: Contextable; + private contextableWhere: Contextable; + private contextablePreOps: Array> = []; + private contextablePostOps: Array> = []; + + private uniqueKeyAssertions: Assertion[] = []; + private rowConditionsAssertion: Assertion; + + constructor(session?: Session, unverifiedConfig?: any, configPath?: string) { + super(session); + this.session = session; + + if (!unverifiedConfig) { + return; + } + + const config = this.verifyConfig(unverifiedConfig); + + if (!config.name) { + config.name = Path.basename(config.filename); + } + const target = actionConfigToCompiledGraphTarget(config); + this.proto.target = this.applySessionToTarget( + target, + session.projectConfig, + config.filename, + true + ); + this.proto.canonicalTarget = this.applySessionToTarget(target, session.canonicalProjectConfig); + + if (configPath) { + config.filename = resolveActionsConfigFilename(config.filename, configPath); + this.query(nativeRequire(config.filename).query); + } + + if (config.dependOnDependencyAssertions) { + this.setDependOnDependencyAssertions(config.dependOnDependencyAssertions); + } + if (config.dependencyTargets) { + this.dependencies(config.dependencyTargets); + } + if (config.hermetic !== undefined) { + this.hermetic(config.hermetic); + } + if (config.disabled) { + this.disabled(); + } + if (Object.keys(config.additionalOptions).length > 0) { + this.bigquery({ additionalOptions: config.additionalOptions }); + } + if (config.tags) { + this.tags(config.tags); + } + if (config.description) { + this.description(config.description); + } + if (config.columns?.length) { + this.columns( + config.columns.map(columnDescriptor => + dataform.ActionConfig.ColumnDescriptor.create(columnDescriptor) + ) + ); + } + if (config.project) { + this.database(config.project); + } + if (config.dataset) { + this.schema(config.dataset); + } + if (config.assertions) { + this.assertions(dataform.ActionConfig.TableAssertionsConfig.create(config.assertions)); + } + if (config.uniqueKey) { + this.uniqueKey(config.uniqueKey); + } + this.protected(config.protected); + if (config.preOperations) { + this.preOps(config.preOperations); + } + if (config.postOperations) { + this.postOps(config.postOperations); + } + this.bigquery({ + partitionBy: config.partitionBy, + clusterBy: config.clusterBy, + updatePartitionFilter: config.updatePartitionFilter, + labels: config.labels, + partitionExpirationDays: config.partitionExpirationDays, + requirePartitionFilter: config.requirePartitionFilter, + additionalOptions: config.additionalOptions + }); + if (config.filename) { + this.proto.fileName = config.filename; + } + + return this; + } + + public query(query: Contextable) { + this.contextableQuery = query; + return this; + } + + public where(where: Contextable) { + this.contextableWhere = where; + return this; + } + + public preOps(pres: Contextable) { + this.contextablePreOps.push(pres); + return this; + } + + public postOps(posts: Contextable) { + this.contextablePostOps.push(posts); + return this; + } + + public disabled(disabled = true) { + this.proto.disabled = disabled; + this.uniqueKeyAssertions.forEach(assertion => assertion.disabled(disabled)); + this.rowConditionsAssertion?.disabled(disabled); + return this; + } + + public protected(defaultsToTrueProtected: boolean) { + // To prevent accidental data deletion, protected defaults to true if unspecified. + if (defaultsToTrueProtected === undefined || defaultsToTrueProtected === null) { + defaultsToTrueProtected = true; + } + this.proto.protected = defaultsToTrueProtected; + return this; + } + + public uniqueKey(uniqueKey: string[]) { + this.proto.uniqueKey = uniqueKey; + } + + public bigquery(bigquery: dataform.IBigQueryOptions) { + if (!!bigquery.labels && Object.keys(bigquery.labels).length > 0) { + if (!this.proto.actionDescriptor) { + this.proto.actionDescriptor = {}; + } + this.proto.actionDescriptor.bigqueryLabels = bigquery.labels; + } + + // Remove all falsy values, to preserve backwards compatability of compiled graph output. + let bigqueryFiltered: dataform.IBigQueryOptions = {}; + Object.entries(bigquery).forEach(([key, value]) => { + if (value) { + bigqueryFiltered = { + ...bigqueryFiltered, + [key]: value + }; + } + }); + if (Object.values(bigqueryFiltered).length > 0) { + this.proto.bigquery = dataform.BigQueryOptions.create(bigqueryFiltered); + } + return this; + } + + public dependencies(value: Resolvable | Resolvable[]) { + const newDependencies = Array.isArray(value) ? value : [value]; + newDependencies.forEach(resolvable => + addDependenciesToActionDependencyTargets(this, resolvable) + ); + return this; + } + + public hermetic(hermetic: boolean) { + this.proto.hermeticity = hermetic + ? dataform.ActionHermeticity.HERMETIC + : dataform.ActionHermeticity.NON_HERMETIC; + } + + public tags(value: string | string[]) { + const newTags = typeof value === "string" ? [value] : value; + newTags.forEach(t => { + this.proto.tags.push(t); + }); + this.uniqueKeyAssertions.forEach(assertion => assertion.tags(value)); + this.rowConditionsAssertion?.tags(value); + return this; + } + + public description(description: string) { + if (!this.proto.actionDescriptor) { + this.proto.actionDescriptor = {}; + } + this.proto.actionDescriptor.description = description; + return this; + } + + public columns(columns: dataform.ActionConfig.ColumnDescriptor[]) { + if (!this.proto.actionDescriptor) { + this.proto.actionDescriptor = {}; + } + this.proto.actionDescriptor.columns = ColumnDescriptors.mapConfigProtoToCompilationProto( + columns + ); + return this; + } + + public database(database: string) { + setNameAndTarget( + this.session, + this.proto, + this.proto.target.name, + this.proto.target.schema, + database + ); + return this; + } + + public schema(schema: string) { + setNameAndTarget( + this.session, + this.proto, + this.proto.target.name, + schema, + this.proto.target.database + ); + return this; + } + + public assertions(assertions: dataform.ActionConfig.TableAssertionsConfig) { + if (!!assertions.uniqueKey?.length && !!assertions.uniqueKeys?.length) { + this.session.compileError( + new Error("Specify at most one of 'assertions.uniqueKey' and 'assertions.uniqueKeys'.") + ); + } + let uniqueKeys = assertions.uniqueKeys.map(uniqueKey => + dataform.ActionConfig.TableAssertionsConfig.UniqueKey.create(uniqueKey) + ); + if (!!assertions.uniqueKey?.length) { + uniqueKeys = [ + dataform.ActionConfig.TableAssertionsConfig.UniqueKey.create({ + uniqueKey: ["TableAssertionsConfig"] + }) + ]; + } + if (uniqueKeys) { + uniqueKeys.forEach(({ uniqueKey }, index) => { + const uniqueKeyAssertion = this.session.assert( + `${this.proto.target.schema}_${this.proto.target.name}_assertions_uniqueKey_${index}`, + ctx => this.session.compilationSql().indexAssertion(ctx.ref(this.proto.target), uniqueKey) + ); + if (this.proto.tags) { + uniqueKeyAssertion.tags(this.proto.tags); + } + uniqueKeyAssertion.proto.parentAction = this.proto.target; + if (this.proto.disabled) { + uniqueKeyAssertion.disabled(); + } + this.uniqueKeyAssertions.push(uniqueKeyAssertion); + }); + } + const mergedRowConditions = assertions.rowConditions || []; + if (!!assertions.nonNull) { + const nonNullCols = + typeof assertions.nonNull === "string" ? [assertions.nonNull] : assertions.nonNull; + nonNullCols.forEach(nonNullCol => mergedRowConditions.push(`${nonNullCol} IS NOT NULL`)); + } + if (!!mergedRowConditions && mergedRowConditions.length > 0) { + this.rowConditionsAssertion = this.session.assert( + `${this.proto.target.schema}_${this.proto.target.name}_assertions_rowConditions`, + ctx => + this.session + .compilationSql() + .rowConditionsAssertion(ctx.ref(this.proto.target), mergedRowConditions) + ); + this.rowConditionsAssertion.proto.parentAction = this.proto.target; + if (this.proto.disabled) { + this.rowConditionsAssertion.disabled(); + } + if (this.proto.tags) { + this.rowConditionsAssertion.tags(this.proto.tags); + } + } + return this; + } + + public setDependOnDependencyAssertions(dependOnDependencyAssertions: boolean) { + this.dependOnDependencyAssertions = dependOnDependencyAssertions; + return this; + } + + /** + * @hidden + */ + public getFileName() { + return this.proto.fileName; + } + + /** + * @hidden + */ + public getTarget() { + return dataform.Target.create(this.proto.target); + } + + public compile() { + const context = new IncrementalTableContext(this); + const incrementalContext = new IncrementalTableContext(this, true); + + this.proto.query = context.apply(this.contextableQuery); + + if (this.proto.enumType === dataform.TableType.INCREMENTAL) { + this.proto.incrementalQuery = incrementalContext.apply(this.contextableQuery); + + this.proto.incrementalPreOps = this.contextifyOps(this.contextablePreOps, incrementalContext); + this.proto.incrementalPostOps = this.contextifyOps( + this.contextablePostOps, + incrementalContext + ); + } + + if (this.contextableWhere) { + this.proto.where = context.apply(this.contextableWhere); + } + + this.proto.preOps = this.contextifyOps(this.contextablePreOps, context).filter( + op => !!op.trim() + ); + this.proto.postOps = this.contextifyOps(this.contextablePostOps, context).filter( + op => !!op.trim() + ); + + validateQueryString(this.session, this.proto.query, this.proto.fileName); + validateQueryString(this.session, this.proto.incrementalQuery, this.proto.fileName); + + return verifyObjectMatchesProto( + dataform.Table, + this.proto, + VerifyProtoErrorBehaviour.SUGGEST_REPORTING_TO_DATAFORM_TEAM + ); + } + + private contextifyOps( + contextableOps: Array>, + currentContext: IncrementalTableContext + ) { + let protoOps: string[] = []; + contextableOps.forEach(contextableOp => { + const appliedOps = currentContext.apply(contextableOp); + protoOps = protoOps.concat(typeof appliedOps === "string" ? [appliedOps] : appliedOps); + }); + return protoOps; + } + + private verifyConfig( + unverifiedConfig: ILegacyIncrementalTableConfig + ): dataform.ActionConfig.IncrementalTableConfig { + // The "type" field only exists on legacy incremental table configs. Here we convert them to the + // new format. + if (unverifiedConfig.type) { + delete unverifiedConfig.type; + if (unverifiedConfig.dependencies) { + unverifiedConfig.dependencyTargets = unverifiedConfig.dependencies.map( + (dependency: string | object) => + typeof dependency === "string" ? { name: dependency } : dependency + ); + delete unverifiedConfig.dependencies; + } + if (unverifiedConfig.database) { + unverifiedConfig.project = unverifiedConfig.database; + delete unverifiedConfig.database; + } + if (unverifiedConfig.schema) { + unverifiedConfig.dataset = unverifiedConfig.schema; + delete unverifiedConfig.schema; + } + if (unverifiedConfig.fileName) { + unverifiedConfig.filename = unverifiedConfig.fileName; + delete unverifiedConfig.fileName; + } + if (unverifiedConfig.columns) { + unverifiedConfig.columns = ColumnDescriptors.mapLegacyObjectToConfigProto( + unverifiedConfig.columns as any + ); + } + if (unverifiedConfig?.assertions) { + if (unverifiedConfig.assertions.uniqueKey) { + unverifiedConfig.assertions.uniqueKey = unverifiedConfig.assertions.uniqueKey; + } + // This determines if the uniqueKeys is of the legacy type. + if (unverifiedConfig.assertions.uniqueKeys?.[0]?.length > 0) { + unverifiedConfig.assertions.uniqueKeys = (unverifiedConfig.assertions + .uniqueKeys as string[][]).map(uniqueKey => + dataform.ActionConfig.TableAssertionsConfig.UniqueKey.create({ uniqueKey }) + ); + } + if (typeof unverifiedConfig.assertions.nonNull === "string") { + unverifiedConfig.assertions.nonNull = [unverifiedConfig.assertions.nonNull]; + } + } + if (unverifiedConfig?.bigquery) { + if (!!unverifiedConfig.bigquery.partitionBy) { + unverifiedConfig.partitionBy = unverifiedConfig.bigquery.partitionBy; + } + if (!!unverifiedConfig.bigquery.clusterBy) { + unverifiedConfig.clusterBy = unverifiedConfig.bigquery.clusterBy; + } + if (!!unverifiedConfig.bigquery.updatePartitionFilter) { + unverifiedConfig.updatePartitionFilter = unverifiedConfig.bigquery.updatePartitionFilter; + } + if (!!unverifiedConfig.bigquery.labels) { + unverifiedConfig.labels = unverifiedConfig.bigquery.labels; + } + if (!!unverifiedConfig.bigquery.partitionExpirationDays) { + unverifiedConfig.partitionExpirationDays = + unverifiedConfig.bigquery.partitionExpirationDays; + } + if (!!unverifiedConfig.bigquery.requirePartitionFilter) { + unverifiedConfig.requirePartitionFilter = + unverifiedConfig.bigquery.requirePartitionFilter; + } + if (!!unverifiedConfig.bigquery.additionalOptions) { + unverifiedConfig.additionalOptions = unverifiedConfig.bigquery.additionalOptions; + } + delete unverifiedConfig.bigquery; + } + } + + return verifyObjectMatchesProto( + dataform.ActionConfig.IncrementalTableConfig, + unverifiedConfig, + VerifyProtoErrorBehaviour.SHOW_DOCS_LINK + ); + } +} + +/** + * @hidden + */ +export class IncrementalTableContext implements ITableContext { + constructor(private table: IncrementalTable, private isIncremental = false) {} + + public self(): string { + return this.resolve(this.table.proto.target); + } + + public name(): string { + return this.table.session.finalizeName(this.table.proto.target.name); + } + + public ref(ref: Resolvable | string[], ...rest: string[]): string { + ref = toResolvable(ref, rest); + if (!resolvableAsTarget(ref)) { + this.table.session.compileError(new Error(`Action name is not specified`)); + return ""; + } + this.table.dependencies(ref); + return this.resolve(ref); + } + + public resolve(ref: Resolvable | string[], ...rest: string[]) { + return this.table.session.resolve(ref, ...rest); + } + + public schema(): string { + return this.table.session.finalizeSchema(this.table.proto.target.schema); + } + + public database(): string { + if (!this.table.proto.target.database) { + this.table.session.compileError(new Error(`Warehouse does not support multiple databases`)); + return ""; + } + + return this.table.session.finalizeDatabase(this.table.proto.target.database); + } + + public where(where: Contextable) { + this.table.where(where); + return ""; + } + + public when(cond: boolean, trueCase: string, falseCase: string = "") { + return cond ? trueCase : falseCase; + } + + public incremental() { + return !!this.isIncremental; + } + + public preOps(statement: Contextable) { + this.table.preOps(statement); + return ""; + } + + public postOps(statement: Contextable) { + this.table.postOps(statement); + return ""; + } + + public disabled() { + this.table.disabled(); + return ""; + } + + public bigquery(bigquery: dataform.IBigQueryOptions) { + this.table.bigquery(bigquery); + return ""; + } + + public dependencies(res: Resolvable) { + this.table.dependencies(res); + return ""; + } + + public apply(value: Contextable): T { + if (typeof value === "function") { + return (value as any)(this); + } else { + return value; + } + } + + public tags(tags: string[]) { + this.table.tags(tags); + return ""; + } +} diff --git a/core/actions/index.ts b/core/actions/index.ts index c0c55084d..29abc9311 100644 --- a/core/actions/index.ts +++ b/core/actions/index.ts @@ -1,5 +1,6 @@ import { Assertion } from "df/core/actions/assertion"; import { Declaration } from "df/core/actions/declaration"; +import { IncrementalTable } from "df/core/actions/incremental_table"; import { Notebook } from "df/core/actions/notebook"; import { Operation } from "df/core/actions/operation"; import { Table } from "df/core/actions/table"; @@ -7,7 +8,14 @@ import { View } from "df/core/actions/view"; import { Session } from "df/core/session"; import { dataform } from "df/protos/ts"; -export type Action = Table | View | Operation | Assertion | Declaration | Notebook; +export type Action = + | Table + | View + | IncrementalTable + | Operation + | Assertion + | Declaration + | Notebook; // TODO(ekrekr): In v4, make all method on inheritors of this private, forcing users to use // constructors in order to populate actions. diff --git a/core/actions/view.ts b/core/actions/view.ts index 028f1a836..9d99afc16 100644 --- a/core/actions/view.ts +++ b/core/actions/view.ts @@ -403,9 +403,6 @@ export class View extends ActionBuilder { delete unverifiedConfig.fileName; } if (unverifiedConfig.columns) { - // TODO(ekrekr) columns in their current config format are a difficult structure to represent - // as protos. They are nested, and use the object keys as the names. Consider a forced - // migration to the proto style column names. unverifiedConfig.columns = ColumnDescriptors.mapLegacyObjectToConfigProto( unverifiedConfig.columns as any ); @@ -448,7 +445,7 @@ export class View extends ActionBuilder { * @hidden */ export class ViewContext implements ITableContext { - constructor(private table: Table | View, private isIncremental = false) {} + constructor(private table: View, private isIncremental = false) {} public self(): string { return this.resolve(this.table.proto.target); diff --git a/core/main.ts b/core/main.ts index 0066ab24b..a20cff6cf 100644 --- a/core/main.ts +++ b/core/main.ts @@ -6,6 +6,7 @@ import { } from "df/common/protos"; import { Assertion } from "df/core/actions/assertion"; import { Declaration } from "df/core/actions/declaration"; +import { IncrementalTable } from "df/core/actions/incremental_table"; import { Notebook } from "df/core/actions/notebook"; import { Operation } from "df/core/actions/operation"; import { Table } from "df/core/actions/table"; @@ -141,10 +142,9 @@ function loadActionConfigs(session: Session, filePaths: string[]) { ); } else if (actionConfig.incrementalTable) { session.actions.push( - new Table( + new IncrementalTable( session, dataform.ActionConfig.IncrementalTableConfig.create(actionConfig.incrementalTable), - "incremental", actionConfigsPath ) ); diff --git a/core/main_test.ts b/core/main_test.ts index 0b7b1a5a6..31dc8356c 100644 --- a/core/main_test.ts +++ b/core/main_test.ts @@ -1041,6 +1041,7 @@ actions: expect(asPlainObject(result.compile.compiledGraph.tables)).deep.equals( asPlainObject([ { + bigquery: {}, target: { database: "defaultProject", schema: "defaultDataset", @@ -1052,6 +1053,7 @@ actions: name: "action" }, fileName: "definitions/action.sql", + hermeticity: "NON_HERMETIC", query: "SELECT 1", incrementalQuery: "SELECT 1", type: "incremental", diff --git a/core/session.ts b/core/session.ts index c131148ba..df21f3e81 100644 --- a/core/session.ts +++ b/core/session.ts @@ -5,6 +5,7 @@ import { StringifiedMap, StringifiedSet } from "df/common/strings/stringifier"; import { Action } from "df/core/actions"; import { AContextable, Assertion, AssertionContext } from "df/core/actions/assertion"; import { Declaration } from "df/core/actions/declaration"; +import { IncrementalTable } from "df/core/actions/incremental_table"; import { Notebook } from "df/core/actions/notebook"; import { Operation, OperationContext } from "df/core/actions/operation"; import { ITableConfig, ITableContext, Table, TableContext, TableType } from "df/core/actions/table"; @@ -148,8 +149,23 @@ export class Session { } this.actions.push(view); break; - case "table": case "incremental": + sqlxConfig.filename = utils.getCallerFile(this.rootDir); + const incrementalTable = new IncrementalTable(this, sqlxConfig).query( + ctx => actionOptions.sqlContextable(ctx)[0] + ); + if (actionOptions.incrementalWhereContextable) { + incrementalTable.where(actionOptions.incrementalWhereContextable); + } + if (actionOptions.preOperationsContextable) { + incrementalTable.preOps(actionOptions.preOperationsContextable); + } + if (actionOptions.postOperationsContextable) { + incrementalTable.postOps(actionOptions.postOperationsContextable); + } + this.actions.push(incrementalTable); + break; + case "table": const table = this.publish(sqlxConfig.name) .config(sqlxConfig) .query(ctx => actionOptions.sqlContextable(ctx)[0]); @@ -340,7 +356,10 @@ export class Session { const compiledGraph = dataform.CompiledGraph.create({ projectConfig: this.projectConfig, tables: this.compileGraphChunk( - this.actions.filter(action => action instanceof Table || action instanceof View) + this.actions.filter( + action => + action instanceof Table || action instanceof View || action instanceof IncrementalTable + ) ), operations: this.compileGraphChunk( this.actions.filter(action => action instanceof Operation) diff --git a/core/utils.ts b/core/utils.ts index 6b31dc3e5..d019954c4 100644 --- a/core/utils.ts +++ b/core/utils.ts @@ -1,5 +1,6 @@ import { Action } from "df/core/actions"; import { Assertion } from "df/core/actions/assertion"; +import { IncrementalTable } from "df/core/actions/incremental_table"; import { Notebook } from "df/core/actions/notebook"; import { Operation } from "df/core/actions/operation"; import { Table } from "df/core/actions/table"; @@ -12,7 +13,7 @@ import { dataform } from "df/protos/ts"; declare var __webpack_require__: any; declare var __non_webpack_require__: any; -type actionsWithDependencies = Table | View | Operation | Notebook; +type actionsWithDependencies = Table | View | IncrementalTable | Operation | Notebook; // This side-steps webpack's require in favour of the real require. export const nativeRequire =