@backstage/backend-defaults 0.13.0-next.2 → 0.13.1-next.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/dist/PackageDiscoveryService.cjs.js +2 -0
  3. package/dist/PackageDiscoveryService.cjs.js.map +1 -1
  4. package/dist/alpha/entrypoints/actions/DefaultActionsService.cjs.js +4 -0
  5. package/dist/alpha/entrypoints/actions/DefaultActionsService.cjs.js.map +1 -1
  6. package/dist/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.cjs.js +5 -1
  7. package/dist/alpha/entrypoints/actionsRegistry/DefaultActionsRegistryService.cjs.js.map +1 -1
  8. package/dist/database.d.ts +1 -1
  9. package/dist/entrypoints/auditor/WinstonRootAuditorService.cjs.js +1 -0
  10. package/dist/entrypoints/auditor/WinstonRootAuditorService.cjs.js.map +1 -1
  11. package/dist/entrypoints/auth/DefaultAuthService.cjs.js +6 -0
  12. package/dist/entrypoints/auth/DefaultAuthService.cjs.js.map +1 -1
  13. package/dist/entrypoints/auth/JwksClient.cjs.js +3 -2
  14. package/dist/entrypoints/auth/JwksClient.cjs.js.map +1 -1
  15. package/dist/entrypoints/auth/external/ExternalAuthTokenHandler.cjs.js +6 -4
  16. package/dist/entrypoints/auth/external/ExternalAuthTokenHandler.cjs.js.map +1 -1
  17. package/dist/entrypoints/auth/plugin/PluginTokenHandler.cjs.js +14 -8
  18. package/dist/entrypoints/auth/plugin/PluginTokenHandler.cjs.js.map +1 -1
  19. package/dist/entrypoints/auth/plugin/keys/DatabaseKeyStore.cjs.js +6 -4
  20. package/dist/entrypoints/auth/plugin/keys/DatabaseKeyStore.cjs.js.map +1 -1
  21. package/dist/entrypoints/auth/plugin/keys/DatabasePluginKeySource.cjs.js +6 -2
  22. package/dist/entrypoints/auth/plugin/keys/DatabasePluginKeySource.cjs.js.map +1 -1
  23. package/dist/entrypoints/auth/plugin/keys/StaticConfigPluginKeySource.cjs.js +2 -0
  24. package/dist/entrypoints/auth/plugin/keys/StaticConfigPluginKeySource.cjs.js.map +1 -1
  25. package/dist/entrypoints/auth/user/UserTokenHandler.cjs.js +6 -4
  26. package/dist/entrypoints/auth/user/UserTokenHandler.cjs.js.map +1 -1
  27. package/dist/entrypoints/database/DatabaseManager.cjs.js +9 -3
  28. package/dist/entrypoints/database/DatabaseManager.cjs.js.map +1 -1
  29. package/dist/entrypoints/database/connectors/mysql.cjs.js +2 -0
  30. package/dist/entrypoints/database/connectors/mysql.cjs.js.map +1 -1
  31. package/dist/entrypoints/database/connectors/postgres.cjs.js +2 -0
  32. package/dist/entrypoints/database/connectors/postgres.cjs.js.map +1 -1
  33. package/dist/entrypoints/database/connectors/sqlite3.cjs.js +1 -0
  34. package/dist/entrypoints/database/connectors/sqlite3.cjs.js.map +1 -1
  35. package/dist/entrypoints/lifecycle/lifecycleServiceFactory.cjs.js +2 -0
  36. package/dist/entrypoints/lifecycle/lifecycleServiceFactory.cjs.js.map +1 -1
  37. package/dist/entrypoints/rootHealth/rootHealthServiceFactory.cjs.js +2 -1
  38. package/dist/entrypoints/rootHealth/rootHealthServiceFactory.cjs.js.map +1 -1
  39. package/dist/entrypoints/rootLifecycle/rootLifecycleServiceFactory.cjs.js +1 -0
  40. package/dist/entrypoints/rootLifecycle/rootLifecycleServiceFactory.cjs.js.map +1 -1
  41. package/dist/entrypoints/scheduler/lib/LocalTaskWorker.cjs.js +9 -6
  42. package/dist/entrypoints/scheduler/lib/LocalTaskWorker.cjs.js.map +1 -1
  43. package/dist/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.cjs.js +11 -8
  44. package/dist/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.cjs.js.map +1 -1
  45. package/dist/entrypoints/scheduler/lib/TaskWorker.cjs.js +9 -4
  46. package/dist/entrypoints/scheduler/lib/TaskWorker.cjs.js.map +1 -1
  47. package/dist/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.cjs.js +8 -5
  48. package/dist/entrypoints/urlReader/lib/AwsCodeCommitUrlReader.cjs.js.map +1 -1
  49. package/dist/entrypoints/urlReader/lib/AwsS3UrlReader.cjs.js +8 -5
  50. package/dist/entrypoints/urlReader/lib/AwsS3UrlReader.cjs.js.map +1 -1
  51. package/dist/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.cjs.js +9 -6
  52. package/dist/entrypoints/urlReader/lib/AzureBlobStorageUrlReader.cjs.js.map +1 -1
  53. package/dist/entrypoints/urlReader/lib/AzureUrlReader.cjs.js +6 -4
  54. package/dist/entrypoints/urlReader/lib/AzureUrlReader.cjs.js.map +1 -1
  55. package/dist/entrypoints/urlReader/lib/BitbucketCloudUrlReader.cjs.js +12 -10
  56. package/dist/entrypoints/urlReader/lib/BitbucketCloudUrlReader.cjs.js.map +1 -1
  57. package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js +6 -4
  58. package/dist/entrypoints/urlReader/lib/BitbucketServerUrlReader.cjs.js.map +1 -1
  59. package/dist/entrypoints/urlReader/lib/BitbucketUrlReader.cjs.js +25 -13
  60. package/dist/entrypoints/urlReader/lib/BitbucketUrlReader.cjs.js.map +1 -1
  61. package/dist/entrypoints/urlReader/lib/GerritUrlReader.cjs.js +6 -4
  62. package/dist/entrypoints/urlReader/lib/GerritUrlReader.cjs.js.map +1 -1
  63. package/dist/entrypoints/urlReader/lib/GiteaUrlReader.cjs.js +6 -4
  64. package/dist/entrypoints/urlReader/lib/GiteaUrlReader.cjs.js.map +1 -1
  65. package/dist/entrypoints/urlReader/lib/GithubUrlReader.cjs.js +11 -9
  66. package/dist/entrypoints/urlReader/lib/GithubUrlReader.cjs.js.map +1 -1
  67. package/dist/entrypoints/urlReader/lib/GitlabUrlReader.cjs.js +6 -4
  68. package/dist/entrypoints/urlReader/lib/GitlabUrlReader.cjs.js.map +1 -1
  69. package/dist/entrypoints/urlReader/lib/GoogleGcsUrlReader.cjs.js +6 -4
  70. package/dist/entrypoints/urlReader/lib/GoogleGcsUrlReader.cjs.js.map +1 -1
  71. package/dist/entrypoints/urlReader/lib/HarnessUrlReader.cjs.js +6 -4
  72. package/dist/entrypoints/urlReader/lib/HarnessUrlReader.cjs.js.map +1 -1
  73. package/dist/entrypoints/urlReader/lib/tree/ReadTreeResponseFactory.cjs.js +4 -3
  74. package/dist/entrypoints/urlReader/lib/tree/ReadTreeResponseFactory.cjs.js.map +1 -1
  75. package/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js +4 -2
  76. package/dist/entrypoints/urlReader/lib/tree/ReadableArrayResponse.cjs.js.map +1 -1
  77. package/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js +7 -1
  78. package/dist/entrypoints/urlReader/lib/tree/TarArchiveResponse.cjs.js.map +1 -1
  79. package/dist/entrypoints/urlReader/lib/tree/ZipArchiveResponse.cjs.js +6 -1
  80. package/dist/entrypoints/urlReader/lib/tree/ZipArchiveResponse.cjs.js.map +1 -1
  81. package/dist/package.json.cjs.js +1 -1
  82. package/dist/urlReader.d.ts +12 -11
  83. package/package.json +14 -14
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite3.cjs.js","sources":["../../../../src/entrypoints/database/connectors/sqlite3.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DevDataStore } from '@backstage/backend-dev-utils';\nimport { LifecycleService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config, ConfigReader } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport { ensureDirSync } from 'fs-extra';\nimport knexFactory, { Knex } from 'knex';\nimport { merge, omit } from 'lodash';\nimport path from 'path';\nimport { Connector } from '../types';\nimport { mergeDatabaseConfig } from './mergeDatabaseConfig';\n\n/**\n * Creates a knex SQLite3 database connection\n *\n * @param dbConfig - The database config\n * @param overrides - Additional options to merge with the config\n */\nexport function createSqliteDatabaseClient(\n pluginId: string,\n dbConfig: Config,\n deps: {\n logger: LoggerService;\n lifecycle: LifecycleService;\n },\n overrides?: Knex.Config,\n) {\n const knexConfig = buildSqliteDatabaseConfig(dbConfig, overrides);\n const connConfig = knexConfig.connection as Knex.Sqlite3ConnectionConfig;\n\n const filename = connConfig.filename ?? ':memory:';\n\n // If storage on disk is used, ensure that the directory exists\n if (filename !== ':memory:') {\n const directory = path.dirname(filename);\n ensureDirSync(directory);\n }\n\n let database: Knex;\n\n if (deps && filename === ':memory:') {\n // The dev store is used during watch mode to store and restore the database\n // across reloads. It is only available when running the backend through\n // `backstage-cli package start`.\n const devStore = DevDataStore.get();\n\n if (devStore) {\n const dataKey = `sqlite3-db-${pluginId}`;\n\n const connectionLoader = async () => {\n // If seed data is available, use it tconnectionLoader restore the database\n const { data: seedData } = await devStore.load(dataKey);\n\n return {\n ...(knexConfig.connection as Knex.Sqlite3ConnectionConfig),\n filename: seedData ?? ':memory:',\n };\n };\n\n database = knexFactory({\n ...knexConfig,\n connection: Object.assign(connectionLoader, {\n // This is a workaround for the knex SQLite driver always warning when using a config loader\n filename: ':memory:',\n }),\n });\n\n // If the dev store is available we save the database state on shutdown\n deps.lifecycle.addShutdownHook(async () => {\n const connection = await database.client.acquireConnection();\n const data = connection.serialize();\n await devStore.save(dataKey, data);\n });\n } else {\n database = knexFactory(knexConfig);\n }\n } else {\n database = knexFactory(knexConfig);\n }\n\n database.client.pool.on('createSuccess', (_eventId: any, resource: any) => {\n resource.run('PRAGMA foreign_keys = ON', () => {});\n });\n\n return database;\n}\n\n/**\n * Builds a knex SQLite3 connection config\n *\n * @param dbConfig - The database config\n * @param overrides - Additional options to merge with the config\n */\nexport function buildSqliteDatabaseConfig(\n dbConfig: Config,\n overrides?: Knex.Config,\n): Knex.Config {\n const baseConfig = dbConfig.get<Knex.Config>();\n\n // Normalize config to always contain a connection object\n if (typeof baseConfig.connection === 'string') {\n baseConfig.connection = { filename: baseConfig.connection };\n }\n if (overrides && typeof overrides.connection === 'string') {\n overrides.connection = { filename: overrides.connection };\n }\n\n const config: Knex.Config = mergeDatabaseConfig(\n {\n connection: {},\n },\n baseConfig,\n {\n useNullAsDefault: true,\n },\n overrides,\n );\n\n return config;\n}\n\n/**\n * Provides a partial knex SQLite3 config to override database name.\n */\nexport function createSqliteNameOverride(name: string): Partial<Knex.Config> {\n return {\n connection: parseSqliteConnectionString(name),\n };\n}\n\n/**\n * Produces a partial knex SQLite3 connection config with database name.\n */\nexport function parseSqliteConnectionString(\n name: string,\n): Knex.Sqlite3ConnectionConfig {\n return {\n filename: name,\n };\n}\n\n/**\n * Provides a config lookup path for a plugin's config block.\n */\nfunction pluginPath(pluginId: string): string {\n return `plugin.${pluginId}`;\n}\n\nfunction normalizeConnection(\n connection: Knex.StaticConnectionConfig | JsonObject | string | undefined,\n): Partial<Knex.StaticConnectionConfig> {\n if (typeof connection === 'undefined' || connection === null) {\n return {};\n }\n\n return typeof connection === 'string' || connection instanceof String\n ? parseSqliteConnectionString(connection as string)\n : connection;\n}\n\nexport class Sqlite3Connector implements Connector {\n constructor(private readonly config: Config) {}\n\n async getClient(\n pluginId: string,\n deps: {\n logger: LoggerService;\n lifecycle: LifecycleService;\n },\n ): Promise<Knex> {\n const pluginConfig = new ConfigReader(\n this.getConfigForPlugin(pluginId) as JsonObject,\n );\n\n const pluginDivisionMode = this.getPluginDivisionModeConfig();\n if (pluginDivisionMode !== 'database') {\n throw new Error(\n `The SQLite driver does not support plugin division mode '${pluginDivisionMode}'`,\n );\n }\n\n const databaseClientOverrides = mergeDatabaseConfig(\n {},\n this.getDatabaseOverrides(pluginId),\n );\n\n const client = createSqliteDatabaseClient(\n pluginId,\n pluginConfig,\n deps,\n databaseClientOverrides,\n );\n\n return client;\n }\n\n /**\n * Provides the canonical database name for a given plugin.\n *\n * This method provides the effective database name which is determined using global\n * and plugin specific database config. If no explicit database name is configured\n * and `pluginDivisionMode` is not `schema`, this method will provide a generated name\n * which is the pluginId prefixed with 'backstage_plugin_'. If `pluginDivisionMode` is\n * `schema`, it will fallback to using the default database for the knex instance.\n *\n * @param pluginId - Lookup the database name for given plugin\n * @returns String representing the plugin's database name\n */\n private getDatabaseName(pluginId: string): string | undefined {\n const connection = this.getConnectionConfig(pluginId);\n\n const sqliteFilename: string | undefined = (\n connection as Knex.Sqlite3ConnectionConfig\n ).filename;\n\n if (sqliteFilename === ':memory:') {\n return sqliteFilename;\n }\n\n const sqliteDirectory =\n (connection as { directory?: string }).directory ?? '.';\n\n return path.join(sqliteDirectory, sqliteFilename ?? `${pluginId}.sqlite`);\n }\n\n /**\n * Provides the client type which should be used for a given plugin.\n *\n * The client type is determined by plugin specific config if present.\n * Otherwise the base client is used as the fallback.\n *\n * @param pluginId - Plugin to get the client type for\n * @returns Object with client type returned as `client` and boolean\n * representing whether or not the client was overridden as\n * `overridden`\n */\n private getClientType(pluginId: string): {\n client: string;\n overridden: boolean;\n } {\n const pluginClient = this.config.getOptionalString(\n `${pluginPath(pluginId)}.client`,\n );\n\n const baseClient = this.config.getString('client');\n const client = pluginClient ?? baseClient;\n return {\n client,\n overridden: client !== baseClient,\n };\n }\n\n private getRoleConfig(pluginId: string): string | undefined {\n return (\n this.config.getOptionalString(`${pluginPath(pluginId)}.role`) ??\n this.config.getOptionalString('role')\n );\n }\n\n /**\n * Provides the knexConfig which should be used for a given plugin.\n *\n * @param pluginId - Plugin to get the knexConfig for\n * @returns The merged knexConfig value or undefined if it isn't specified\n */\n private getAdditionalKnexConfig(pluginId: string): JsonObject | undefined {\n const pluginConfig = this.config\n .getOptionalConfig(`${pluginPath(pluginId)}.knexConfig`)\n ?.get<JsonObject>();\n\n const baseConfig = this.config\n .getOptionalConfig('knexConfig')\n ?.get<JsonObject>();\n\n return merge(baseConfig, pluginConfig);\n }\n\n private getPluginDivisionModeConfig(): string {\n return this.config.getOptionalString('pluginDivisionMode') ?? 'database';\n }\n\n /**\n * Provides a Knex connection plugin config by combining base and plugin\n * config.\n *\n * This method provides a baseConfig for a plugin database connector. If the\n * client type has not been overridden, the global connection config will be\n * included with plugin specific config as the base. Values from the plugin\n * connection take precedence over the base. Base database name is omitted for\n * all supported databases excluding SQLite unless `pluginDivisionMode` is set\n * to `schema`.\n */\n private getConnectionConfig(pluginId: string): Knex.StaticConnectionConfig {\n const { client, overridden } = this.getClientType(pluginId);\n\n let baseConnection = normalizeConnection(this.config.get('connection'));\n\n if (\n client.includes('sqlite3') &&\n 'filename' in baseConnection &&\n baseConnection.filename !== ':memory:'\n ) {\n throw new Error(\n '`connection.filename` is not supported for the base sqlite connection. Prefer `connection.directory` or provide a filename for the plugin connection instead.',\n );\n }\n\n // Databases cannot be shared unless the `pluginDivisionMode` is set to `schema`. The\n // `database` property from the base connection is omitted unless `pluginDivisionMode`\n // is set to `schema`. SQLite3's `filename` property is an exception as this is used as a\n // directory elsewhere so we preserve `filename`.\n if (this.getPluginDivisionModeConfig() !== 'schema') {\n baseConnection = omit(baseConnection, 'database');\n }\n\n // get and normalize optional plugin specific database connection\n const connection = normalizeConnection(\n this.config.getOptional(`${pluginPath(pluginId)}.connection`),\n );\n\n return {\n // include base connection if client type has not been overridden\n ...(overridden ? {} : baseConnection),\n ...connection,\n } as Knex.StaticConnectionConfig;\n }\n\n /**\n * Provides a Knex database config for a given plugin.\n *\n * This method provides a Knex configuration object along with the plugin's\n * client type.\n *\n * @param pluginId - The plugin that the database config should correspond with\n */\n private getConfigForPlugin(pluginId: string): Knex.Config {\n const { client } = this.getClientType(pluginId);\n const role = this.getRoleConfig(pluginId);\n\n return {\n ...this.getAdditionalKnexConfig(pluginId),\n client,\n connection: this.getConnectionConfig(pluginId),\n ...(role && { role }),\n };\n }\n\n /**\n * Provides a partial `Knex.Config`• database name override for a given plugin.\n *\n * @param pluginId - Target plugin to get database name override\n * @returns Partial `Knex.Config` with database name override\n */\n private getDatabaseOverrides(pluginId: string): Knex.Config {\n const databaseName = this.getDatabaseName(pluginId);\n return databaseName ? createSqliteNameOverride(databaseName) : {};\n }\n}\n"],"names":["path","ensureDirSync","DevDataStore","knexFactory","mergeDatabaseConfig","ConfigReader","merge","omit"],"mappings":"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"}
1
+ {"version":3,"file":"sqlite3.cjs.js","sources":["../../../../src/entrypoints/database/connectors/sqlite3.ts"],"sourcesContent":["/*\n * Copyright 2020 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { DevDataStore } from '@backstage/backend-dev-utils';\nimport { LifecycleService, LoggerService } from '@backstage/backend-plugin-api';\nimport { Config, ConfigReader } from '@backstage/config';\nimport { JsonObject } from '@backstage/types';\nimport { ensureDirSync } from 'fs-extra';\nimport knexFactory, { Knex } from 'knex';\nimport { merge, omit } from 'lodash';\nimport path from 'path';\nimport { Connector } from '../types';\nimport { mergeDatabaseConfig } from './mergeDatabaseConfig';\n\n/**\n * Creates a knex SQLite3 database connection\n *\n * @param dbConfig - The database config\n * @param overrides - Additional options to merge with the config\n */\nexport function createSqliteDatabaseClient(\n pluginId: string,\n dbConfig: Config,\n deps: {\n logger: LoggerService;\n lifecycle: LifecycleService;\n },\n overrides?: Knex.Config,\n) {\n const knexConfig = buildSqliteDatabaseConfig(dbConfig, overrides);\n const connConfig = knexConfig.connection as Knex.Sqlite3ConnectionConfig;\n\n const filename = connConfig.filename ?? ':memory:';\n\n // If storage on disk is used, ensure that the directory exists\n if (filename !== ':memory:') {\n const directory = path.dirname(filename);\n ensureDirSync(directory);\n }\n\n let database: Knex;\n\n if (deps && filename === ':memory:') {\n // The dev store is used during watch mode to store and restore the database\n // across reloads. It is only available when running the backend through\n // `backstage-cli package start`.\n const devStore = DevDataStore.get();\n\n if (devStore) {\n const dataKey = `sqlite3-db-${pluginId}`;\n\n const connectionLoader = async () => {\n // If seed data is available, use it tconnectionLoader restore the database\n const { data: seedData } = await devStore.load(dataKey);\n\n return {\n ...(knexConfig.connection as Knex.Sqlite3ConnectionConfig),\n filename: seedData ?? ':memory:',\n };\n };\n\n database = knexFactory({\n ...knexConfig,\n connection: Object.assign(connectionLoader, {\n // This is a workaround for the knex SQLite driver always warning when using a config loader\n filename: ':memory:',\n }),\n });\n\n // If the dev store is available we save the database state on shutdown\n deps.lifecycle.addShutdownHook(async () => {\n const connection = await database.client.acquireConnection();\n const data = connection.serialize();\n await devStore.save(dataKey, data);\n });\n } else {\n database = knexFactory(knexConfig);\n }\n } else {\n database = knexFactory(knexConfig);\n }\n\n database.client.pool.on('createSuccess', (_eventId: any, resource: any) => {\n resource.run('PRAGMA foreign_keys = ON', () => {});\n });\n\n return database;\n}\n\n/**\n * Builds a knex SQLite3 connection config\n *\n * @param dbConfig - The database config\n * @param overrides - Additional options to merge with the config\n */\nexport function buildSqliteDatabaseConfig(\n dbConfig: Config,\n overrides?: Knex.Config,\n): Knex.Config {\n const baseConfig = dbConfig.get<Knex.Config>();\n\n // Normalize config to always contain a connection object\n if (typeof baseConfig.connection === 'string') {\n baseConfig.connection = { filename: baseConfig.connection };\n }\n if (overrides && typeof overrides.connection === 'string') {\n overrides.connection = { filename: overrides.connection };\n }\n\n const config: Knex.Config = mergeDatabaseConfig(\n {\n connection: {},\n },\n baseConfig,\n {\n useNullAsDefault: true,\n },\n overrides,\n );\n\n return config;\n}\n\n/**\n * Provides a partial knex SQLite3 config to override database name.\n */\nexport function createSqliteNameOverride(name: string): Partial<Knex.Config> {\n return {\n connection: parseSqliteConnectionString(name),\n };\n}\n\n/**\n * Produces a partial knex SQLite3 connection config with database name.\n */\nexport function parseSqliteConnectionString(\n name: string,\n): Knex.Sqlite3ConnectionConfig {\n return {\n filename: name,\n };\n}\n\n/**\n * Provides a config lookup path for a plugin's config block.\n */\nfunction pluginPath(pluginId: string): string {\n return `plugin.${pluginId}`;\n}\n\nfunction normalizeConnection(\n connection: Knex.StaticConnectionConfig | JsonObject | string | undefined,\n): Partial<Knex.StaticConnectionConfig> {\n if (typeof connection === 'undefined' || connection === null) {\n return {};\n }\n\n return typeof connection === 'string' || connection instanceof String\n ? parseSqliteConnectionString(connection as string)\n : connection;\n}\n\nexport class Sqlite3Connector implements Connector {\n private readonly config: Config;\n\n constructor(config: Config) {\n this.config = config;\n }\n\n async getClient(\n pluginId: string,\n deps: {\n logger: LoggerService;\n lifecycle: LifecycleService;\n },\n ): Promise<Knex> {\n const pluginConfig = new ConfigReader(\n this.getConfigForPlugin(pluginId) as JsonObject,\n );\n\n const pluginDivisionMode = this.getPluginDivisionModeConfig();\n if (pluginDivisionMode !== 'database') {\n throw new Error(\n `The SQLite driver does not support plugin division mode '${pluginDivisionMode}'`,\n );\n }\n\n const databaseClientOverrides = mergeDatabaseConfig(\n {},\n this.getDatabaseOverrides(pluginId),\n );\n\n const client = createSqliteDatabaseClient(\n pluginId,\n pluginConfig,\n deps,\n databaseClientOverrides,\n );\n\n return client;\n }\n\n /**\n * Provides the canonical database name for a given plugin.\n *\n * This method provides the effective database name which is determined using global\n * and plugin specific database config. If no explicit database name is configured\n * and `pluginDivisionMode` is not `schema`, this method will provide a generated name\n * which is the pluginId prefixed with 'backstage_plugin_'. If `pluginDivisionMode` is\n * `schema`, it will fallback to using the default database for the knex instance.\n *\n * @param pluginId - Lookup the database name for given plugin\n * @returns String representing the plugin's database name\n */\n private getDatabaseName(pluginId: string): string | undefined {\n const connection = this.getConnectionConfig(pluginId);\n\n const sqliteFilename: string | undefined = (\n connection as Knex.Sqlite3ConnectionConfig\n ).filename;\n\n if (sqliteFilename === ':memory:') {\n return sqliteFilename;\n }\n\n const sqliteDirectory =\n (connection as { directory?: string }).directory ?? '.';\n\n return path.join(sqliteDirectory, sqliteFilename ?? `${pluginId}.sqlite`);\n }\n\n /**\n * Provides the client type which should be used for a given plugin.\n *\n * The client type is determined by plugin specific config if present.\n * Otherwise the base client is used as the fallback.\n *\n * @param pluginId - Plugin to get the client type for\n * @returns Object with client type returned as `client` and boolean\n * representing whether or not the client was overridden as\n * `overridden`\n */\n private getClientType(pluginId: string): {\n client: string;\n overridden: boolean;\n } {\n const pluginClient = this.config.getOptionalString(\n `${pluginPath(pluginId)}.client`,\n );\n\n const baseClient = this.config.getString('client');\n const client = pluginClient ?? baseClient;\n return {\n client,\n overridden: client !== baseClient,\n };\n }\n\n private getRoleConfig(pluginId: string): string | undefined {\n return (\n this.config.getOptionalString(`${pluginPath(pluginId)}.role`) ??\n this.config.getOptionalString('role')\n );\n }\n\n /**\n * Provides the knexConfig which should be used for a given plugin.\n *\n * @param pluginId - Plugin to get the knexConfig for\n * @returns The merged knexConfig value or undefined if it isn't specified\n */\n private getAdditionalKnexConfig(pluginId: string): JsonObject | undefined {\n const pluginConfig = this.config\n .getOptionalConfig(`${pluginPath(pluginId)}.knexConfig`)\n ?.get<JsonObject>();\n\n const baseConfig = this.config\n .getOptionalConfig('knexConfig')\n ?.get<JsonObject>();\n\n return merge(baseConfig, pluginConfig);\n }\n\n private getPluginDivisionModeConfig(): string {\n return this.config.getOptionalString('pluginDivisionMode') ?? 'database';\n }\n\n /**\n * Provides a Knex connection plugin config by combining base and plugin\n * config.\n *\n * This method provides a baseConfig for a plugin database connector. If the\n * client type has not been overridden, the global connection config will be\n * included with plugin specific config as the base. Values from the plugin\n * connection take precedence over the base. Base database name is omitted for\n * all supported databases excluding SQLite unless `pluginDivisionMode` is set\n * to `schema`.\n */\n private getConnectionConfig(pluginId: string): Knex.StaticConnectionConfig {\n const { client, overridden } = this.getClientType(pluginId);\n\n let baseConnection = normalizeConnection(this.config.get('connection'));\n\n if (\n client.includes('sqlite3') &&\n 'filename' in baseConnection &&\n baseConnection.filename !== ':memory:'\n ) {\n throw new Error(\n '`connection.filename` is not supported for the base sqlite connection. Prefer `connection.directory` or provide a filename for the plugin connection instead.',\n );\n }\n\n // Databases cannot be shared unless the `pluginDivisionMode` is set to `schema`. The\n // `database` property from the base connection is omitted unless `pluginDivisionMode`\n // is set to `schema`. SQLite3's `filename` property is an exception as this is used as a\n // directory elsewhere so we preserve `filename`.\n if (this.getPluginDivisionModeConfig() !== 'schema') {\n baseConnection = omit(baseConnection, 'database');\n }\n\n // get and normalize optional plugin specific database connection\n const connection = normalizeConnection(\n this.config.getOptional(`${pluginPath(pluginId)}.connection`),\n );\n\n return {\n // include base connection if client type has not been overridden\n ...(overridden ? {} : baseConnection),\n ...connection,\n } as Knex.StaticConnectionConfig;\n }\n\n /**\n * Provides a Knex database config for a given plugin.\n *\n * This method provides a Knex configuration object along with the plugin's\n * client type.\n *\n * @param pluginId - The plugin that the database config should correspond with\n */\n private getConfigForPlugin(pluginId: string): Knex.Config {\n const { client } = this.getClientType(pluginId);\n const role = this.getRoleConfig(pluginId);\n\n return {\n ...this.getAdditionalKnexConfig(pluginId),\n client,\n connection: this.getConnectionConfig(pluginId),\n ...(role && { role }),\n };\n }\n\n /**\n * Provides a partial `Knex.Config`• database name override for a given plugin.\n *\n * @param pluginId - Target plugin to get database name override\n * @returns Partial `Knex.Config` with database name override\n */\n private getDatabaseOverrides(pluginId: string): Knex.Config {\n const databaseName = this.getDatabaseName(pluginId);\n return databaseName ? createSqliteNameOverride(databaseName) : {};\n }\n}\n"],"names":["path","ensureDirSync","DevDataStore","knexFactory","mergeDatabaseConfig","ConfigReader","merge","omit"],"mappings":"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"}
@@ -3,6 +3,8 @@
3
3
  var backendPluginApi = require('@backstage/backend-plugin-api');
4
4
 
5
5
  class BackendPluginLifecycleImpl {
6
+ logger;
7
+ pluginMetadata;
6
8
  constructor(logger, pluginMetadata) {
7
9
  this.logger = logger;
8
10
  this.pluginMetadata = pluginMetadata;
@@ -1 +1 @@
1
- {"version":3,"file":"lifecycleServiceFactory.cjs.js","sources":["../../../src/entrypoints/lifecycle/lifecycleServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n LifecycleService,\n LifecycleServiceShutdownHook,\n LifecycleServiceShutdownOptions,\n LifecycleServiceStartupHook,\n LifecycleServiceStartupOptions,\n LoggerService,\n PluginMetadataService,\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\n\n/** @internal */\nexport class BackendPluginLifecycleImpl implements LifecycleService {\n constructor(\n private readonly logger: LoggerService,\n private readonly pluginMetadata: PluginMetadataService,\n ) {}\n\n #hasStarted = false;\n #hasShutdown = false;\n #startupTasks: Array<{\n hook: LifecycleServiceStartupHook;\n options?: LifecycleServiceStartupOptions;\n }> = [];\n #shutdownTasks: Array<{\n hook: LifecycleServiceShutdownHook;\n options?: LifecycleServiceShutdownOptions;\n }> = [];\n\n addStartupHook(\n hook: LifecycleServiceStartupHook,\n options?: LifecycleServiceStartupOptions,\n ): void {\n if (this.#hasStarted) {\n throw new Error('Attempted to add startup hook after startup');\n }\n this.#startupTasks.push({ hook, options });\n }\n\n async startup(): Promise<void> {\n if (this.#hasStarted) {\n return;\n }\n this.#hasStarted = true;\n\n this.logger.debug(\n `Running ${this.#startupTasks.length} plugin startup tasks...`,\n );\n await Promise.all(\n this.#startupTasks.map(async ({ hook, options }) => {\n const logger = options?.logger ?? this.logger;\n try {\n await hook();\n logger.debug(`Plugin startup hook succeeded`);\n } catch (error) {\n logger.error(`Plugin startup hook failed, ${error}`);\n }\n }),\n );\n }\n\n addShutdownHook(\n hook: LifecycleServiceShutdownHook,\n options?: LifecycleServiceShutdownOptions,\n ): void {\n if (this.#hasShutdown) {\n throw new Error('Attempted to add shutdown hook after shutdown');\n }\n const plugin = this.pluginMetadata.getId();\n const logger = options?.logger?.child({ plugin }) ?? this.logger;\n this.#shutdownTasks.push({\n hook,\n options: {\n ...options,\n logger,\n },\n });\n }\n\n async shutdown(): Promise<void> {\n if (this.#hasShutdown) {\n return;\n }\n this.#hasShutdown = true;\n\n this.logger.debug(\n `Running ${this.#shutdownTasks.length} plugin shutdown tasks...`,\n );\n\n await Promise.all(\n this.#shutdownTasks.map(async ({ hook, options }) => {\n const logger = options?.logger ?? this.logger;\n try {\n await hook();\n logger.debug(`Plugin shutdown hook succeeded`);\n } catch (error) {\n logger.error('Plugin shutdown hook failed', error);\n }\n }),\n );\n }\n}\n\n/**\n * Registration of plugin startup and shutdown lifecycle hooks.\n *\n * See {@link @backstage/code-plugin-api#LifecycleService}\n * and {@link https://backstage.io/docs/backend-system/core-services/lifecycle | the service docs}\n * for more information.\n *\n * @public\n */\nexport const lifecycleServiceFactory = createServiceFactory({\n service: coreServices.lifecycle,\n deps: {\n logger: coreServices.logger,\n pluginMetadata: coreServices.pluginMetadata,\n },\n async factory({ logger, pluginMetadata }) {\n return new BackendPluginLifecycleImpl(logger, pluginMetadata);\n },\n});\n"],"names":["createServiceFactory","coreServices"],"mappings":"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"}
1
+ {"version":3,"file":"lifecycleServiceFactory.cjs.js","sources":["../../../src/entrypoints/lifecycle/lifecycleServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n LifecycleService,\n LifecycleServiceShutdownHook,\n LifecycleServiceShutdownOptions,\n LifecycleServiceStartupHook,\n LifecycleServiceStartupOptions,\n LoggerService,\n PluginMetadataService,\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\n\n/** @internal */\nexport class BackendPluginLifecycleImpl implements LifecycleService {\n private readonly logger: LoggerService;\n private readonly pluginMetadata: PluginMetadataService;\n\n constructor(logger: LoggerService, pluginMetadata: PluginMetadataService) {\n this.logger = logger;\n this.pluginMetadata = pluginMetadata;\n }\n\n #hasStarted = false;\n #hasShutdown = false;\n #startupTasks: Array<{\n hook: LifecycleServiceStartupHook;\n options?: LifecycleServiceStartupOptions;\n }> = [];\n #shutdownTasks: Array<{\n hook: LifecycleServiceShutdownHook;\n options?: LifecycleServiceShutdownOptions;\n }> = [];\n\n addStartupHook(\n hook: LifecycleServiceStartupHook,\n options?: LifecycleServiceStartupOptions,\n ): void {\n if (this.#hasStarted) {\n throw new Error('Attempted to add startup hook after startup');\n }\n this.#startupTasks.push({ hook, options });\n }\n\n async startup(): Promise<void> {\n if (this.#hasStarted) {\n return;\n }\n this.#hasStarted = true;\n\n this.logger.debug(\n `Running ${this.#startupTasks.length} plugin startup tasks...`,\n );\n await Promise.all(\n this.#startupTasks.map(async ({ hook, options }) => {\n const logger = options?.logger ?? this.logger;\n try {\n await hook();\n logger.debug(`Plugin startup hook succeeded`);\n } catch (error) {\n logger.error(`Plugin startup hook failed, ${error}`);\n }\n }),\n );\n }\n\n addShutdownHook(\n hook: LifecycleServiceShutdownHook,\n options?: LifecycleServiceShutdownOptions,\n ): void {\n if (this.#hasShutdown) {\n throw new Error('Attempted to add shutdown hook after shutdown');\n }\n const plugin = this.pluginMetadata.getId();\n const logger = options?.logger?.child({ plugin }) ?? this.logger;\n this.#shutdownTasks.push({\n hook,\n options: {\n ...options,\n logger,\n },\n });\n }\n\n async shutdown(): Promise<void> {\n if (this.#hasShutdown) {\n return;\n }\n this.#hasShutdown = true;\n\n this.logger.debug(\n `Running ${this.#shutdownTasks.length} plugin shutdown tasks...`,\n );\n\n await Promise.all(\n this.#shutdownTasks.map(async ({ hook, options }) => {\n const logger = options?.logger ?? this.logger;\n try {\n await hook();\n logger.debug(`Plugin shutdown hook succeeded`);\n } catch (error) {\n logger.error('Plugin shutdown hook failed', error);\n }\n }),\n );\n }\n}\n\n/**\n * Registration of plugin startup and shutdown lifecycle hooks.\n *\n * See {@link @backstage/code-plugin-api#LifecycleService}\n * and {@link https://backstage.io/docs/backend-system/core-services/lifecycle | the service docs}\n * for more information.\n *\n * @public\n */\nexport const lifecycleServiceFactory = createServiceFactory({\n service: coreServices.lifecycle,\n deps: {\n logger: coreServices.logger,\n pluginMetadata: coreServices.pluginMetadata,\n },\n async factory({ logger, pluginMetadata }) {\n return new BackendPluginLifecycleImpl(logger, pluginMetadata);\n },\n});\n"],"names":["createServiceFactory","coreServices"],"mappings":"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"}
@@ -3,6 +3,8 @@
3
3
  var backendPluginApi = require('@backstage/backend-plugin-api');
4
4
 
5
5
  class DefaultRootHealthService {
6
+ #state = "init";
7
+ options;
6
8
  constructor(options) {
7
9
  this.options = options;
8
10
  options.lifecycle.addStartupHook(() => {
@@ -12,7 +14,6 @@ class DefaultRootHealthService {
12
14
  this.#state = "down";
13
15
  });
14
16
  }
15
- #state = "init";
16
17
  async getLiveness() {
17
18
  return { status: 200, payload: { status: "ok" } };
18
19
  }
@@ -1 +1 @@
1
- {"version":3,"file":"rootHealthServiceFactory.cjs.js","sources":["../../../src/entrypoints/rootHealth/rootHealthServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n RootHealthService,\n RootLifecycleService,\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\n\n/** @internal */\nexport class DefaultRootHealthService implements RootHealthService {\n #state: 'init' | 'up' | 'down' = 'init';\n\n constructor(readonly options: { lifecycle: RootLifecycleService }) {\n options.lifecycle.addStartupHook(() => {\n this.#state = 'up';\n });\n options.lifecycle.addBeforeShutdownHook(() => {\n this.#state = 'down';\n });\n }\n\n async getLiveness(): Promise<{ status: number; payload?: any }> {\n return { status: 200, payload: { status: 'ok' } };\n }\n\n async getReadiness(): Promise<{ status: number; payload?: any }> {\n if (this.#state === 'init') {\n return {\n status: 503,\n payload: { message: 'Backend has not started yet', status: 'error' },\n };\n }\n if (this.#state === 'down') {\n return {\n status: 503,\n payload: { message: 'Backend is shuttting down', status: 'error' },\n };\n }\n\n return { status: 200, payload: { status: 'ok' } };\n }\n}\n\n/**\n * @public\n */\nexport const rootHealthServiceFactory = createServiceFactory({\n service: coreServices.rootHealth,\n deps: {\n lifecycle: coreServices.rootLifecycle,\n },\n async factory({ lifecycle }) {\n return new DefaultRootHealthService({ lifecycle });\n },\n});\n"],"names":["createServiceFactory","coreServices"],"mappings":";;;;AAwBO,MAAM,wBAAA,CAAsD;AAAA,EAGjE,YAAqB,OAAA,EAA8C;AAA9C,IAAA,IAAA,CAAA,OAAA,GAAA,OAAA;AACnB,IAAA,OAAA,CAAQ,SAAA,CAAU,eAAe,MAAM;AACrC,MAAA,IAAA,CAAK,MAAA,GAAS,IAAA;AAAA,IAChB,CAAC,CAAA;AACD,IAAA,OAAA,CAAQ,SAAA,CAAU,sBAAsB,MAAM;AAC5C,MAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,IAChB,CAAC,CAAA;AAAA,EACH;AAAA,EATA,MAAA,GAAiC,MAAA;AAAA,EAWjC,MAAM,WAAA,GAA0D;AAC9D,IAAA,OAAO,EAAE,MAAA,EAAQ,GAAA,EAAK,SAAS,EAAE,MAAA,EAAQ,MAAK,EAAE;AAAA,EAClD;AAAA,EAEA,MAAM,YAAA,GAA2D;AAC/D,IAAA,IAAI,IAAA,CAAK,WAAW,MAAA,EAAQ;AAC1B,MAAA,OAAO;AAAA,QACL,MAAA,EAAQ,GAAA;AAAA,QACR,OAAA,EAAS,EAAE,OAAA,EAAS,6BAAA,EAA+B,QAAQ,OAAA;AAAQ,OACrE;AAAA,IACF;AACA,IAAA,IAAI,IAAA,CAAK,WAAW,MAAA,EAAQ;AAC1B,MAAA,OAAO;AAAA,QACL,MAAA,EAAQ,GAAA;AAAA,QACR,OAAA,EAAS,EAAE,OAAA,EAAS,2BAAA,EAA6B,QAAQ,OAAA;AAAQ,OACnE;AAAA,IACF;AAEA,IAAA,OAAO,EAAE,MAAA,EAAQ,GAAA,EAAK,SAAS,EAAE,MAAA,EAAQ,MAAK,EAAE;AAAA,EAClD;AACF;AAKO,MAAM,2BAA2BA,qCAAA,CAAqB;AAAA,EAC3D,SAASC,6BAAA,CAAa,UAAA;AAAA,EACtB,IAAA,EAAM;AAAA,IACJ,WAAWA,6BAAA,CAAa;AAAA,GAC1B;AAAA,EACA,MAAM,OAAA,CAAQ,EAAE,SAAA,EAAU,EAAG;AAC3B,IAAA,OAAO,IAAI,wBAAA,CAAyB,EAAE,SAAA,EAAW,CAAA;AAAA,EACnD;AACF,CAAC;;;;;"}
1
+ {"version":3,"file":"rootHealthServiceFactory.cjs.js","sources":["../../../src/entrypoints/rootHealth/rootHealthServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2024 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n RootHealthService,\n RootLifecycleService,\n coreServices,\n createServiceFactory,\n} from '@backstage/backend-plugin-api';\n\n/** @internal */\nexport class DefaultRootHealthService implements RootHealthService {\n #state: 'init' | 'up' | 'down' = 'init';\n readonly options: { lifecycle: RootLifecycleService };\n\n constructor(options: { lifecycle: RootLifecycleService }) {\n this.options = options;\n options.lifecycle.addStartupHook(() => {\n this.#state = 'up';\n });\n options.lifecycle.addBeforeShutdownHook(() => {\n this.#state = 'down';\n });\n }\n\n async getLiveness(): Promise<{ status: number; payload?: any }> {\n return { status: 200, payload: { status: 'ok' } };\n }\n\n async getReadiness(): Promise<{ status: number; payload?: any }> {\n if (this.#state === 'init') {\n return {\n status: 503,\n payload: { message: 'Backend has not started yet', status: 'error' },\n };\n }\n if (this.#state === 'down') {\n return {\n status: 503,\n payload: { message: 'Backend is shuttting down', status: 'error' },\n };\n }\n\n return { status: 200, payload: { status: 'ok' } };\n }\n}\n\n/**\n * @public\n */\nexport const rootHealthServiceFactory = createServiceFactory({\n service: coreServices.rootHealth,\n deps: {\n lifecycle: coreServices.rootLifecycle,\n },\n async factory({ lifecycle }) {\n return new DefaultRootHealthService({ lifecycle });\n },\n});\n"],"names":["createServiceFactory","coreServices"],"mappings":";;;;AAwBO,MAAM,wBAAA,CAAsD;AAAA,EACjE,MAAA,GAAiC,MAAA;AAAA,EACxB,OAAA;AAAA,EAET,YAAY,OAAA,EAA8C;AACxD,IAAA,IAAA,CAAK,OAAA,GAAU,OAAA;AACf,IAAA,OAAA,CAAQ,SAAA,CAAU,eAAe,MAAM;AACrC,MAAA,IAAA,CAAK,MAAA,GAAS,IAAA;AAAA,IAChB,CAAC,CAAA;AACD,IAAA,OAAA,CAAQ,SAAA,CAAU,sBAAsB,MAAM;AAC5C,MAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,IAChB,CAAC,CAAA;AAAA,EACH;AAAA,EAEA,MAAM,WAAA,GAA0D;AAC9D,IAAA,OAAO,EAAE,MAAA,EAAQ,GAAA,EAAK,SAAS,EAAE,MAAA,EAAQ,MAAK,EAAE;AAAA,EAClD;AAAA,EAEA,MAAM,YAAA,GAA2D;AAC/D,IAAA,IAAI,IAAA,CAAK,WAAW,MAAA,EAAQ;AAC1B,MAAA,OAAO;AAAA,QACL,MAAA,EAAQ,GAAA;AAAA,QACR,OAAA,EAAS,EAAE,OAAA,EAAS,6BAAA,EAA+B,QAAQ,OAAA;AAAQ,OACrE;AAAA,IACF;AACA,IAAA,IAAI,IAAA,CAAK,WAAW,MAAA,EAAQ;AAC1B,MAAA,OAAO;AAAA,QACL,MAAA,EAAQ,GAAA;AAAA,QACR,OAAA,EAAS,EAAE,OAAA,EAAS,2BAAA,EAA6B,QAAQ,OAAA;AAAQ,OACnE;AAAA,IACF;AAEA,IAAA,OAAO,EAAE,MAAA,EAAQ,GAAA,EAAK,SAAS,EAAE,MAAA,EAAQ,MAAK,EAAE;AAAA,EAClD;AACF;AAKO,MAAM,2BAA2BA,qCAAA,CAAqB;AAAA,EAC3D,SAASC,6BAAA,CAAa,UAAA;AAAA,EACtB,IAAA,EAAM;AAAA,IACJ,WAAWA,6BAAA,CAAa;AAAA,GAC1B;AAAA,EACA,MAAM,OAAA,CAAQ,EAAE,SAAA,EAAU,EAAG;AAC3B,IAAA,OAAO,IAAI,wBAAA,CAAyB,EAAE,SAAA,EAAW,CAAA;AAAA,EACnD;AACF,CAAC;;;;;"}
@@ -3,6 +3,7 @@
3
3
  var backendPluginApi = require('@backstage/backend-plugin-api');
4
4
 
5
5
  class BackendLifecycleImpl {
6
+ logger;
6
7
  constructor(logger) {
7
8
  this.logger = logger;
8
9
  }
@@ -1 +1 @@
1
- {"version":3,"file":"rootLifecycleServiceFactory.cjs.js","sources":["../../../src/entrypoints/rootLifecycle/rootLifecycleServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n createServiceFactory,\n coreServices,\n LifecycleServiceStartupHook,\n LifecycleServiceStartupOptions,\n LifecycleServiceShutdownHook,\n LifecycleServiceShutdownOptions,\n RootLifecycleService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\n\n/** @internal */\nexport class BackendLifecycleImpl implements RootLifecycleService {\n constructor(private readonly logger: LoggerService) {}\n\n #hasStarted = false;\n #startupTasks: Array<{\n hook: LifecycleServiceStartupHook;\n options?: LifecycleServiceStartupOptions;\n }> = [];\n\n addStartupHook(\n hook: LifecycleServiceStartupHook,\n options?: LifecycleServiceStartupOptions,\n ): void {\n if (this.#hasStarted) {\n throw new Error('Attempted to add startup hook after startup');\n }\n this.#startupTasks.push({ hook, options });\n }\n\n async startup(): Promise<void> {\n if (this.#hasStarted) {\n return;\n }\n this.#hasStarted = true;\n\n this.logger.debug(`Running ${this.#startupTasks.length} startup tasks...`);\n await Promise.all(\n this.#startupTasks.map(async ({ hook, options }) => {\n const logger = options?.logger ?? this.logger;\n try {\n await hook();\n logger.debug(`Startup hook succeeded`);\n } catch (error) {\n logger.error(`Startup hook failed, ${error}`);\n }\n }),\n );\n }\n\n #hasBeforeShutdown = false;\n #beforeShutdownTasks: Array<{ hook: () => void | Promise<void> }> = [];\n\n addBeforeShutdownHook(hook: () => void): void {\n if (this.#hasBeforeShutdown) {\n throw new Error(\n 'Attempt to add before shutdown hook after shutdown has started',\n );\n }\n this.#beforeShutdownTasks.push({ hook });\n }\n\n async beforeShutdown(): Promise<void> {\n if (this.#hasBeforeShutdown) {\n return;\n }\n this.#hasBeforeShutdown = true;\n\n this.logger.debug(\n `Running ${this.#beforeShutdownTasks.length} before shutdown tasks...`,\n );\n await Promise.all(\n this.#beforeShutdownTasks.map(async ({ hook }) => {\n try {\n await hook();\n this.logger.debug(`Before shutdown hook succeeded`);\n } catch (error) {\n this.logger.error(`Before shutdown hook failed, ${error}`);\n }\n }),\n );\n }\n\n #hasShutdown = false;\n #shutdownTasks: Array<{\n hook: LifecycleServiceShutdownHook;\n options?: LifecycleServiceShutdownOptions;\n }> = [];\n\n addShutdownHook(\n hook: LifecycleServiceShutdownHook,\n options?: LifecycleServiceShutdownOptions,\n ): void {\n if (this.#hasShutdown) {\n throw new Error('Attempted to add shutdown hook after shutdown');\n }\n this.#shutdownTasks.push({ hook, options });\n }\n\n async shutdown(): Promise<void> {\n if (this.#hasShutdown) {\n return;\n }\n this.#hasShutdown = true;\n\n this.logger.debug(\n `Running ${this.#shutdownTasks.length} shutdown tasks...`,\n );\n await Promise.all(\n this.#shutdownTasks.map(async ({ hook, options }) => {\n const logger = options?.logger ?? this.logger;\n try {\n await hook();\n logger.debug(`Shutdown hook succeeded`);\n } catch (error) {\n logger.error(`Shutdown hook failed, ${error}`);\n }\n }),\n );\n }\n}\n\n/**\n * Registration of backend startup and shutdown lifecycle hooks.\n *\n * See {@link @backstage/code-plugin-api#RootLifecycleService}\n * and {@link https://backstage.io/docs/backend-system/core-services/root-lifecycle | the service docs}\n * for more information.\n *\n * @public\n */\nexport const rootLifecycleServiceFactory = createServiceFactory({\n service: coreServices.rootLifecycle,\n deps: {\n logger: coreServices.rootLogger,\n },\n async factory({ logger }) {\n return new BackendLifecycleImpl(logger);\n },\n});\n"],"names":["createServiceFactory","coreServices"],"mappings":"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"}
1
+ {"version":3,"file":"rootLifecycleServiceFactory.cjs.js","sources":["../../../src/entrypoints/rootLifecycle/rootLifecycleServiceFactory.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n createServiceFactory,\n coreServices,\n LifecycleServiceStartupHook,\n LifecycleServiceStartupOptions,\n LifecycleServiceShutdownHook,\n LifecycleServiceShutdownOptions,\n RootLifecycleService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\n\n/** @internal */\nexport class BackendLifecycleImpl implements RootLifecycleService {\n private readonly logger: LoggerService;\n\n constructor(logger: LoggerService) {\n this.logger = logger;\n }\n\n #hasStarted = false;\n #startupTasks: Array<{\n hook: LifecycleServiceStartupHook;\n options?: LifecycleServiceStartupOptions;\n }> = [];\n\n addStartupHook(\n hook: LifecycleServiceStartupHook,\n options?: LifecycleServiceStartupOptions,\n ): void {\n if (this.#hasStarted) {\n throw new Error('Attempted to add startup hook after startup');\n }\n this.#startupTasks.push({ hook, options });\n }\n\n async startup(): Promise<void> {\n if (this.#hasStarted) {\n return;\n }\n this.#hasStarted = true;\n\n this.logger.debug(`Running ${this.#startupTasks.length} startup tasks...`);\n await Promise.all(\n this.#startupTasks.map(async ({ hook, options }) => {\n const logger = options?.logger ?? this.logger;\n try {\n await hook();\n logger.debug(`Startup hook succeeded`);\n } catch (error) {\n logger.error(`Startup hook failed, ${error}`);\n }\n }),\n );\n }\n\n #hasBeforeShutdown = false;\n #beforeShutdownTasks: Array<{ hook: () => void | Promise<void> }> = [];\n\n addBeforeShutdownHook(hook: () => void): void {\n if (this.#hasBeforeShutdown) {\n throw new Error(\n 'Attempt to add before shutdown hook after shutdown has started',\n );\n }\n this.#beforeShutdownTasks.push({ hook });\n }\n\n async beforeShutdown(): Promise<void> {\n if (this.#hasBeforeShutdown) {\n return;\n }\n this.#hasBeforeShutdown = true;\n\n this.logger.debug(\n `Running ${this.#beforeShutdownTasks.length} before shutdown tasks...`,\n );\n await Promise.all(\n this.#beforeShutdownTasks.map(async ({ hook }) => {\n try {\n await hook();\n this.logger.debug(`Before shutdown hook succeeded`);\n } catch (error) {\n this.logger.error(`Before shutdown hook failed, ${error}`);\n }\n }),\n );\n }\n\n #hasShutdown = false;\n #shutdownTasks: Array<{\n hook: LifecycleServiceShutdownHook;\n options?: LifecycleServiceShutdownOptions;\n }> = [];\n\n addShutdownHook(\n hook: LifecycleServiceShutdownHook,\n options?: LifecycleServiceShutdownOptions,\n ): void {\n if (this.#hasShutdown) {\n throw new Error('Attempted to add shutdown hook after shutdown');\n }\n this.#shutdownTasks.push({ hook, options });\n }\n\n async shutdown(): Promise<void> {\n if (this.#hasShutdown) {\n return;\n }\n this.#hasShutdown = true;\n\n this.logger.debug(\n `Running ${this.#shutdownTasks.length} shutdown tasks...`,\n );\n await Promise.all(\n this.#shutdownTasks.map(async ({ hook, options }) => {\n const logger = options?.logger ?? this.logger;\n try {\n await hook();\n logger.debug(`Shutdown hook succeeded`);\n } catch (error) {\n logger.error(`Shutdown hook failed, ${error}`);\n }\n }),\n );\n }\n}\n\n/**\n * Registration of backend startup and shutdown lifecycle hooks.\n *\n * See {@link @backstage/code-plugin-api#RootLifecycleService}\n * and {@link https://backstage.io/docs/backend-system/core-services/root-lifecycle | the service docs}\n * for more information.\n *\n * @public\n */\nexport const rootLifecycleServiceFactory = createServiceFactory({\n service: coreServices.rootLifecycle,\n deps: {\n logger: coreServices.rootLogger,\n },\n async factory({ logger }) {\n return new BackendLifecycleImpl(logger);\n },\n});\n"],"names":["createServiceFactory","coreServices"],"mappings":";;;;AA4BO,MAAM,oBAAA,CAAqD;AAAA,EAC/C,MAAA;AAAA,EAEjB,YAAY,MAAA,EAAuB;AACjC,IAAA,IAAA,CAAK,MAAA,GAAS,MAAA;AAAA,EAChB;AAAA,EAEA,WAAA,GAAc,KAAA;AAAA,EACd,gBAGK,EAAC;AAAA,EAEN,cAAA,CACE,MACA,OAAA,EACM;AACN,IAAA,IAAI,KAAK,WAAA,EAAa;AACpB,MAAA,MAAM,IAAI,MAAM,6CAA6C,CAAA;AAAA,IAC/D;AACA,IAAA,IAAA,CAAK,aAAA,CAAc,IAAA,CAAK,EAAE,IAAA,EAAM,SAAS,CAAA;AAAA,EAC3C;AAAA,EAEA,MAAM,OAAA,GAAyB;AAC7B,IAAA,IAAI,KAAK,WAAA,EAAa;AACpB,MAAA;AAAA,IACF;AACA,IAAA,IAAA,CAAK,WAAA,GAAc,IAAA;AAEnB,IAAA,IAAA,CAAK,OAAO,KAAA,CAAM,CAAA,QAAA,EAAW,IAAA,CAAK,aAAA,CAAc,MAAM,CAAA,iBAAA,CAAmB,CAAA;AACzE,IAAA,MAAM,OAAA,CAAQ,GAAA;AAAA,MACZ,KAAK,aAAA,CAAc,GAAA,CAAI,OAAO,EAAE,IAAA,EAAM,SAAQ,KAAM;AAClD,QAAA,MAAM,MAAA,GAAS,OAAA,EAAS,MAAA,IAAU,IAAA,CAAK,MAAA;AACvC,QAAA,IAAI;AACF,UAAA,MAAM,IAAA,EAAK;AACX,UAAA,MAAA,CAAO,MAAM,CAAA,sBAAA,CAAwB,CAAA;AAAA,QACvC,SAAS,KAAA,EAAO;AACd,UAAA,MAAA,CAAO,KAAA,CAAM,CAAA,qBAAA,EAAwB,KAAK,CAAA,CAAE,CAAA;AAAA,QAC9C;AAAA,MACF,CAAC;AAAA,KACH;AAAA,EACF;AAAA,EAEA,kBAAA,GAAqB,KAAA;AAAA,EACrB,uBAAoE,EAAC;AAAA,EAErE,sBAAsB,IAAA,EAAwB;AAC5C,IAAA,IAAI,KAAK,kBAAA,EAAoB;AAC3B,MAAA,MAAM,IAAI,KAAA;AAAA,QACR;AAAA,OACF;AAAA,IACF;AACA,IAAA,IAAA,CAAK,oBAAA,CAAqB,IAAA,CAAK,EAAE,IAAA,EAAM,CAAA;AAAA,EACzC;AAAA,EAEA,MAAM,cAAA,GAAgC;AACpC,IAAA,IAAI,KAAK,kBAAA,EAAoB;AAC3B,MAAA;AAAA,IACF;AACA,IAAA,IAAA,CAAK,kBAAA,GAAqB,IAAA;AAE1B,IAAA,IAAA,CAAK,MAAA,CAAO,KAAA;AAAA,MACV,CAAA,QAAA,EAAW,IAAA,CAAK,oBAAA,CAAqB,MAAM,CAAA,yBAAA;AAAA,KAC7C;AACA,IAAA,MAAM,OAAA,CAAQ,GAAA;AAAA,MACZ,KAAK,oBAAA,CAAqB,GAAA,CAAI,OAAO,EAAE,MAAK,KAAM;AAChD,QAAA,IAAI;AACF,UAAA,MAAM,IAAA,EAAK;AACX,UAAA,IAAA,CAAK,MAAA,CAAO,MAAM,CAAA,8BAAA,CAAgC,CAAA;AAAA,QACpD,SAAS,KAAA,EAAO;AACd,UAAA,IAAA,CAAK,MAAA,CAAO,KAAA,CAAM,CAAA,6BAAA,EAAgC,KAAK,CAAA,CAAE,CAAA;AAAA,QAC3D;AAAA,MACF,CAAC;AAAA,KACH;AAAA,EACF;AAAA,EAEA,YAAA,GAAe,KAAA;AAAA,EACf,iBAGK,EAAC;AAAA,EAEN,eAAA,CACE,MACA,OAAA,EACM;AACN,IAAA,IAAI,KAAK,YAAA,EAAc;AACrB,MAAA,MAAM,IAAI,MAAM,+CAA+C,CAAA;AAAA,IACjE;AACA,IAAA,IAAA,CAAK,cAAA,CAAe,IAAA,CAAK,EAAE,IAAA,EAAM,SAAS,CAAA;AAAA,EAC5C;AAAA,EAEA,MAAM,QAAA,GAA0B;AAC9B,IAAA,IAAI,KAAK,YAAA,EAAc;AACrB,MAAA;AAAA,IACF;AACA,IAAA,IAAA,CAAK,YAAA,GAAe,IAAA;AAEpB,IAAA,IAAA,CAAK,MAAA,CAAO,KAAA;AAAA,MACV,CAAA,QAAA,EAAW,IAAA,CAAK,cAAA,CAAe,MAAM,CAAA,kBAAA;AAAA,KACvC;AACA,IAAA,MAAM,OAAA,CAAQ,GAAA;AAAA,MACZ,KAAK,cAAA,CAAe,GAAA,CAAI,OAAO,EAAE,IAAA,EAAM,SAAQ,KAAM;AACnD,QAAA,MAAM,MAAA,GAAS,OAAA,EAAS,MAAA,IAAU,IAAA,CAAK,MAAA;AACvC,QAAA,IAAI;AACF,UAAA,MAAM,IAAA,EAAK;AACX,UAAA,MAAA,CAAO,MAAM,CAAA,uBAAA,CAAyB,CAAA;AAAA,QACxC,SAAS,KAAA,EAAO;AACd,UAAA,MAAA,CAAO,KAAA,CAAM,CAAA,sBAAA,EAAyB,KAAK,CAAA,CAAE,CAAA;AAAA,QAC/C;AAAA,MACF,CAAC;AAAA,KACH;AAAA,EACF;AACF;AAWO,MAAM,8BAA8BA,qCAAA,CAAqB;AAAA,EAC9D,SAASC,6BAAA,CAAa,aAAA;AAAA,EACtB,IAAA,EAAM;AAAA,IACJ,QAAQA,6BAAA,CAAa;AAAA,GACvB;AAAA,EACA,MAAM,OAAA,CAAQ,EAAE,MAAA,EAAO,EAAG;AACxB,IAAA,OAAO,IAAI,qBAAqB,MAAM,CAAA;AAAA,EACxC;AACF,CAAC;;;;;"}
@@ -6,11 +6,6 @@ var luxon = require('luxon');
6
6
  var util = require('./util.cjs.js');
7
7
 
8
8
  class LocalTaskWorker {
9
- constructor(taskId, fn, logger) {
10
- this.taskId = taskId;
11
- this.fn = fn;
12
- this.logger = logger;
13
- }
14
9
  abortWait;
15
10
  #taskState = {
16
11
  status: "idle"
@@ -18,9 +13,17 @@ class LocalTaskWorker {
18
13
  #workerState = {
19
14
  status: "idle"
20
15
  };
16
+ taskId;
17
+ fn;
18
+ logger;
19
+ constructor(taskId, fn, logger) {
20
+ this.taskId = taskId;
21
+ this.fn = fn;
22
+ this.logger = logger;
23
+ }
21
24
  start(settings, options) {
22
25
  this.logger.info(
23
- `Task worker starting: ${this.taskId}, ${JSON.stringify(settings)}`
26
+ `Registered scheduled task: ${this.taskId}, ${JSON.stringify(settings)}`
24
27
  );
25
28
  (async () => {
26
29
  let attemptNum = 1;
@@ -1 +1 @@
1
- {"version":3,"file":"LocalTaskWorker.cjs.js","sources":["../../../../src/entrypoints/scheduler/lib/LocalTaskWorker.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { SchedulerServiceTaskFunction } from '@backstage/backend-plugin-api';\nimport { ConflictError } from '@backstage/errors';\nimport { CronTime } from 'cron';\nimport { DateTime, Duration } from 'luxon';\nimport { TaskSettingsV2, TaskApiTasksResponse } from './types';\nimport { delegateAbortController, serializeError, sleep } from './util';\n\n/**\n * Implements tasks that run locally without cross-host collaboration.\n *\n * @private\n */\nexport class LocalTaskWorker {\n private abortWait: AbortController | undefined;\n #taskState: Exclude<TaskApiTasksResponse['taskState'], null> = {\n status: 'idle',\n };\n #workerState: TaskApiTasksResponse['workerState'] = {\n status: 'idle',\n };\n\n constructor(\n private readonly taskId: string,\n private readonly fn: SchedulerServiceTaskFunction,\n private readonly logger: LoggerService,\n ) {}\n\n start(settings: TaskSettingsV2, options: { signal: AbortSignal }) {\n this.logger.info(\n `Task worker starting: ${this.taskId}, ${JSON.stringify(settings)}`,\n );\n\n (async () => {\n let attemptNum = 1;\n for (;;) {\n try {\n await this.performInitialWait(settings, options.signal);\n\n while (!options.signal.aborted) {\n const startTime = process.hrtime();\n await this.runOnce(settings, options.signal);\n const timeTaken = process.hrtime(startTime);\n await this.waitUntilNext(\n settings,\n (timeTaken[0] + timeTaken[1] / 1e9) * 1000,\n options.signal,\n );\n }\n\n this.logger.info(`Task worker finished: ${this.taskId}`);\n attemptNum = 0;\n break;\n } catch (e) {\n attemptNum += 1;\n this.logger.warn(\n `Task worker failed unexpectedly, attempt number ${attemptNum}, ${e}`,\n );\n await sleep(Duration.fromObject({ seconds: 1 }));\n }\n }\n })();\n }\n\n trigger(): void {\n if (!this.abortWait) {\n throw new ConflictError(`Task ${this.taskId} is currently running`);\n }\n this.abortWait.abort();\n }\n\n taskState(): TaskApiTasksResponse['taskState'] {\n return this.#taskState;\n }\n\n workerState(): TaskApiTasksResponse['workerState'] {\n return this.#workerState;\n }\n\n /**\n * Does the once-at-startup initial wait, if configured.\n */\n private async performInitialWait(\n settings: TaskSettingsV2,\n signal: AbortSignal,\n ): Promise<void> {\n if (settings.initialDelayDuration) {\n const parsedDuration = Duration.fromISO(settings.initialDelayDuration);\n\n this.#taskState = {\n status: 'idle',\n startsAt: DateTime.utc().plus(parsedDuration).toISO()!,\n lastRunEndedAt: this.#taskState.lastRunEndedAt,\n lastRunError: this.#taskState.lastRunError,\n };\n this.#workerState = {\n status: 'initial-wait',\n };\n\n await this.sleep(parsedDuration, signal);\n }\n }\n\n /**\n * Makes a single attempt at running the task to completion.\n */\n private async runOnce(\n settings: TaskSettingsV2,\n signal: AbortSignal,\n ): Promise<void> {\n // Abort the task execution either if the worker is stopped, or if the\n // task timeout is hit\n const taskAbortController = delegateAbortController(signal);\n const timeoutDuration = Duration.fromISO(settings.timeoutAfterDuration);\n const timeoutHandle = setTimeout(() => {\n taskAbortController.abort();\n }, timeoutDuration.as('milliseconds'));\n\n this.#taskState = {\n status: 'running',\n startedAt: DateTime.utc().toISO()!,\n timesOutAt: DateTime.utc().plus(timeoutDuration).toISO()!,\n lastRunEndedAt: this.#taskState.lastRunEndedAt,\n lastRunError: this.#taskState.lastRunError,\n };\n this.#workerState = {\n status: 'running',\n };\n\n try {\n await this.fn(taskAbortController.signal);\n this.#taskState.lastRunEndedAt = DateTime.utc().toISO()!;\n this.#taskState.lastRunError = undefined;\n } catch (e) {\n this.#taskState.lastRunEndedAt = DateTime.utc().toISO()!;\n this.#taskState.lastRunError = serializeError(e);\n }\n\n // release resources\n clearTimeout(timeoutHandle);\n taskAbortController.abort();\n }\n\n /**\n * Sleeps until it's time to run the task again.\n */\n private async waitUntilNext(\n settings: TaskSettingsV2,\n lastRunMillis: number,\n signal: AbortSignal,\n ) {\n if (signal.aborted) {\n return;\n }\n\n const isCron = !settings.cadence.startsWith('P');\n let dt: number;\n\n if (isCron) {\n const nextRun = +new CronTime(settings.cadence).sendAt().toJSDate();\n dt = nextRun - Date.now();\n } else {\n dt =\n Duration.fromISO(settings.cadence).as('milliseconds') - lastRunMillis;\n }\n\n dt = Math.max(dt, 0);\n const startsAt = DateTime.now().plus(Duration.fromMillis(dt));\n\n this.#taskState = {\n status: 'idle',\n startsAt: startsAt.toISO()!,\n lastRunEndedAt: this.#taskState.lastRunEndedAt,\n lastRunError: this.#taskState.lastRunError,\n };\n this.#workerState = {\n status: 'idle',\n };\n\n this.logger.debug(\n `task: ${this.taskId} will next occur around ${startsAt}`,\n );\n\n await this.sleep(Duration.fromMillis(dt), signal);\n }\n\n private async sleep(\n duration: Duration,\n abortSignal: AbortSignal,\n ): Promise<void> {\n this.abortWait = delegateAbortController(abortSignal);\n await sleep(duration, this.abortWait.signal);\n this.abortWait.abort(); // cleans up resources\n this.abortWait = undefined;\n }\n}\n"],"names":["sleep","Duration","ConflictError","DateTime","delegateAbortController","serializeError","CronTime"],"mappings":"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"}
1
+ {"version":3,"file":"LocalTaskWorker.cjs.js","sources":["../../../../src/entrypoints/scheduler/lib/LocalTaskWorker.ts"],"sourcesContent":["/*\n * Copyright 2022 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { SchedulerServiceTaskFunction } from '@backstage/backend-plugin-api';\nimport { ConflictError } from '@backstage/errors';\nimport { CronTime } from 'cron';\nimport { DateTime, Duration } from 'luxon';\nimport { TaskSettingsV2, TaskApiTasksResponse } from './types';\nimport { delegateAbortController, serializeError, sleep } from './util';\n\n/**\n * Implements tasks that run locally without cross-host collaboration.\n *\n * @private\n */\nexport class LocalTaskWorker {\n private abortWait: AbortController | undefined;\n #taskState: Exclude<TaskApiTasksResponse['taskState'], null> = {\n status: 'idle',\n };\n #workerState: TaskApiTasksResponse['workerState'] = {\n status: 'idle',\n };\n\n private readonly taskId: string;\n private readonly fn: SchedulerServiceTaskFunction;\n private readonly logger: LoggerService;\n\n constructor(\n taskId: string,\n fn: SchedulerServiceTaskFunction,\n logger: LoggerService,\n ) {\n this.taskId = taskId;\n this.fn = fn;\n this.logger = logger;\n }\n\n start(settings: TaskSettingsV2, options: { signal: AbortSignal }) {\n this.logger.info(\n `Registered scheduled task: ${this.taskId}, ${JSON.stringify(settings)}`,\n );\n\n (async () => {\n let attemptNum = 1;\n for (;;) {\n try {\n await this.performInitialWait(settings, options.signal);\n\n while (!options.signal.aborted) {\n const startTime = process.hrtime();\n await this.runOnce(settings, options.signal);\n const timeTaken = process.hrtime(startTime);\n await this.waitUntilNext(\n settings,\n (timeTaken[0] + timeTaken[1] / 1e9) * 1000,\n options.signal,\n );\n }\n\n this.logger.info(`Task worker finished: ${this.taskId}`);\n attemptNum = 0;\n break;\n } catch (e) {\n attemptNum += 1;\n this.logger.warn(\n `Task worker failed unexpectedly, attempt number ${attemptNum}, ${e}`,\n );\n await sleep(Duration.fromObject({ seconds: 1 }));\n }\n }\n })();\n }\n\n trigger(): void {\n if (!this.abortWait) {\n throw new ConflictError(`Task ${this.taskId} is currently running`);\n }\n this.abortWait.abort();\n }\n\n taskState(): TaskApiTasksResponse['taskState'] {\n return this.#taskState;\n }\n\n workerState(): TaskApiTasksResponse['workerState'] {\n return this.#workerState;\n }\n\n /**\n * Does the once-at-startup initial wait, if configured.\n */\n private async performInitialWait(\n settings: TaskSettingsV2,\n signal: AbortSignal,\n ): Promise<void> {\n if (settings.initialDelayDuration) {\n const parsedDuration = Duration.fromISO(settings.initialDelayDuration);\n\n this.#taskState = {\n status: 'idle',\n startsAt: DateTime.utc().plus(parsedDuration).toISO()!,\n lastRunEndedAt: this.#taskState.lastRunEndedAt,\n lastRunError: this.#taskState.lastRunError,\n };\n this.#workerState = {\n status: 'initial-wait',\n };\n\n await this.sleep(parsedDuration, signal);\n }\n }\n\n /**\n * Makes a single attempt at running the task to completion.\n */\n private async runOnce(\n settings: TaskSettingsV2,\n signal: AbortSignal,\n ): Promise<void> {\n // Abort the task execution either if the worker is stopped, or if the\n // task timeout is hit\n const taskAbortController = delegateAbortController(signal);\n const timeoutDuration = Duration.fromISO(settings.timeoutAfterDuration);\n const timeoutHandle = setTimeout(() => {\n taskAbortController.abort();\n }, timeoutDuration.as('milliseconds'));\n\n this.#taskState = {\n status: 'running',\n startedAt: DateTime.utc().toISO()!,\n timesOutAt: DateTime.utc().plus(timeoutDuration).toISO()!,\n lastRunEndedAt: this.#taskState.lastRunEndedAt,\n lastRunError: this.#taskState.lastRunError,\n };\n this.#workerState = {\n status: 'running',\n };\n\n try {\n await this.fn(taskAbortController.signal);\n this.#taskState.lastRunEndedAt = DateTime.utc().toISO()!;\n this.#taskState.lastRunError = undefined;\n } catch (e) {\n this.#taskState.lastRunEndedAt = DateTime.utc().toISO()!;\n this.#taskState.lastRunError = serializeError(e);\n }\n\n // release resources\n clearTimeout(timeoutHandle);\n taskAbortController.abort();\n }\n\n /**\n * Sleeps until it's time to run the task again.\n */\n private async waitUntilNext(\n settings: TaskSettingsV2,\n lastRunMillis: number,\n signal: AbortSignal,\n ) {\n if (signal.aborted) {\n return;\n }\n\n const isCron = !settings.cadence.startsWith('P');\n let dt: number;\n\n if (isCron) {\n const nextRun = +new CronTime(settings.cadence).sendAt().toJSDate();\n dt = nextRun - Date.now();\n } else {\n dt =\n Duration.fromISO(settings.cadence).as('milliseconds') - lastRunMillis;\n }\n\n dt = Math.max(dt, 0);\n const startsAt = DateTime.now().plus(Duration.fromMillis(dt));\n\n this.#taskState = {\n status: 'idle',\n startsAt: startsAt.toISO()!,\n lastRunEndedAt: this.#taskState.lastRunEndedAt,\n lastRunError: this.#taskState.lastRunError,\n };\n this.#workerState = {\n status: 'idle',\n };\n\n this.logger.debug(\n `task: ${this.taskId} will next occur around ${startsAt}`,\n );\n\n await this.sleep(Duration.fromMillis(dt), signal);\n }\n\n private async sleep(\n duration: Duration,\n abortSignal: AbortSignal,\n ): Promise<void> {\n this.abortWait = delegateAbortController(abortSignal);\n await sleep(duration, this.abortWait.signal);\n this.abortWait.abort(); // cleans up resources\n this.abortWait = undefined;\n }\n}\n"],"names":["sleep","Duration","ConflictError","DateTime","delegateAbortController","serializeError","CronTime"],"mappings":"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"}
@@ -13,6 +13,17 @@ var Router__default = /*#__PURE__*/_interopDefaultCompat(Router);
13
13
 
14
14
  const tracer = api.trace.getTracer(util.TRACER_ID);
15
15
  class PluginTaskSchedulerImpl {
16
+ localWorkersById = /* @__PURE__ */ new Map();
17
+ globalWorkersById = /* @__PURE__ */ new Map();
18
+ allScheduledTasks = [];
19
+ shutdownInitiated;
20
+ counter;
21
+ duration;
22
+ lastStarted;
23
+ lastCompleted;
24
+ pluginId;
25
+ databaseFactory;
26
+ logger;
16
27
  constructor(pluginId, databaseFactory, logger, rootLifecycle) {
17
28
  this.pluginId = pluginId;
18
29
  this.databaseFactory = databaseFactory;
@@ -40,14 +51,6 @@ class PluginTaskSchedulerImpl {
40
51
  rootLifecycle.addShutdownHook(() => shutdownInitiated(true));
41
52
  });
42
53
  }
43
- localWorkersById = /* @__PURE__ */ new Map();
44
- globalWorkersById = /* @__PURE__ */ new Map();
45
- allScheduledTasks = [];
46
- shutdownInitiated;
47
- counter;
48
- duration;
49
- lastStarted;
50
- lastCompleted;
51
54
  async triggerTask(id) {
52
55
  const localTask = this.localWorkersById.get(id);
53
56
  if (localTask) {
@@ -1 +1 @@
1
- {"version":3,"file":"PluginTaskSchedulerImpl.cjs.js","sources":["../../../../src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n LoggerService,\n RootLifecycleService,\n SchedulerService,\n SchedulerServiceTaskDescriptor,\n SchedulerServiceTaskFunction,\n SchedulerServiceTaskInvocationDefinition,\n SchedulerServiceTaskRunner,\n SchedulerServiceTaskScheduleDefinition,\n} from '@backstage/backend-plugin-api';\nimport { Counter, Histogram, Gauge, metrics, trace } from '@opentelemetry/api';\nimport { Knex } from 'knex';\nimport { Duration } from 'luxon';\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport { LocalTaskWorker } from './LocalTaskWorker';\nimport { TaskWorker } from './TaskWorker';\nimport { TaskSettingsV2, TaskApiTasksResponse } from './types';\nimport { delegateAbortController, TRACER_ID, validateId } from './util';\n\nconst tracer = trace.getTracer(TRACER_ID);\n\n/**\n * Implements the actual task management.\n */\nexport class PluginTaskSchedulerImpl implements SchedulerService {\n private readonly localWorkersById = new Map<string, LocalTaskWorker>();\n private readonly globalWorkersById = new Map<string, TaskWorker>();\n private readonly allScheduledTasks: SchedulerServiceTaskDescriptor[] = [];\n private readonly shutdownInitiated: Promise<boolean>;\n\n private readonly counter: Counter;\n private readonly duration: Histogram;\n private readonly lastStarted: Gauge;\n private readonly lastCompleted: Gauge;\n\n constructor(\n private readonly pluginId: string,\n private readonly databaseFactory: () => Promise<Knex>,\n private readonly logger: LoggerService,\n rootLifecycle: RootLifecycleService,\n ) {\n const meter = metrics.getMeter('default');\n this.counter = meter.createCounter('backend_tasks.task.runs.count', {\n description: 'Total number of times a task has been run',\n });\n this.duration = meter.createHistogram('backend_tasks.task.runs.duration', {\n description: 'Histogram of task run durations',\n unit: 'seconds',\n });\n this.lastStarted = meter.createGauge('backend_tasks.task.runs.started', {\n description: 'Epoch timestamp seconds when the task was last started',\n unit: 'seconds',\n });\n this.lastCompleted = meter.createGauge(\n 'backend_tasks.task.runs.completed',\n {\n description: 'Epoch timestamp seconds when the task was last completed',\n unit: 'seconds',\n },\n );\n this.shutdownInitiated = new Promise(shutdownInitiated => {\n rootLifecycle.addShutdownHook(() => shutdownInitiated(true));\n });\n }\n\n async triggerTask(id: string): Promise<void> {\n const localTask = this.localWorkersById.get(id);\n if (localTask) {\n localTask.trigger();\n return;\n }\n\n const knex = await this.databaseFactory();\n await TaskWorker.trigger(knex, id);\n }\n\n async scheduleTask(\n task: SchedulerServiceTaskScheduleDefinition &\n SchedulerServiceTaskInvocationDefinition,\n ): Promise<void> {\n validateId(task.id);\n const scope = task.scope ?? 'global';\n\n const settings: TaskSettingsV2 = {\n version: 2,\n cadence: parseDuration(task.frequency),\n initialDelayDuration:\n task.initialDelay && parseDuration(task.initialDelay),\n timeoutAfterDuration: parseDuration(task.timeout),\n };\n\n // Delegated abort controller that will abort either when the provided\n // controller aborts, or when a root lifecycle shutdown happens\n const abortController = delegateAbortController(task.signal);\n this.shutdownInitiated.then(() => abortController.abort());\n\n if (scope === 'global') {\n const knex = await this.databaseFactory();\n const worker = new TaskWorker(\n task.id,\n this.instrumentedFunction(task, scope),\n knex,\n this.logger.child({ task: task.id }),\n );\n await worker.start(settings, { signal: abortController.signal });\n this.globalWorkersById.set(task.id, worker);\n } else {\n const worker = new LocalTaskWorker(\n task.id,\n this.instrumentedFunction(task, scope),\n this.logger.child({ task: task.id }),\n );\n worker.start(settings, { signal: abortController.signal });\n this.localWorkersById.set(task.id, worker);\n }\n\n this.allScheduledTasks.push({\n id: task.id,\n scope: scope,\n settings: settings,\n });\n }\n\n createScheduledTaskRunner(\n schedule: SchedulerServiceTaskScheduleDefinition,\n ): SchedulerServiceTaskRunner {\n return {\n run: async task => {\n await this.scheduleTask({ ...task, ...schedule });\n },\n };\n }\n\n async getScheduledTasks(): Promise<SchedulerServiceTaskDescriptor[]> {\n return this.allScheduledTasks;\n }\n\n getRouter(): express.Router {\n const router = Router();\n\n router.get('/.backstage/scheduler/v1/tasks', async (_, res) => {\n const globalState = await TaskWorker.taskStates(\n await this.databaseFactory(),\n );\n\n const tasks = new Array<TaskApiTasksResponse>();\n for (const task of this.allScheduledTasks) {\n tasks.push({\n taskId: task.id,\n pluginId: this.pluginId,\n scope: task.scope,\n settings: task.settings,\n taskState:\n this.localWorkersById.get(task.id)?.taskState() ??\n globalState.get(task.id) ??\n null,\n workerState:\n this.localWorkersById.get(task.id)?.workerState() ??\n this.globalWorkersById.get(task.id)?.workerState() ??\n null,\n });\n }\n\n res.json({ tasks });\n });\n\n router.post(\n '/.backstage/scheduler/v1/tasks/:id/trigger',\n async (req, res) => {\n const { id } = req.params;\n await this.triggerTask(id);\n res.status(200).end();\n },\n );\n\n return router;\n }\n\n private instrumentedFunction(\n task: SchedulerServiceTaskInvocationDefinition,\n scope: string,\n ): SchedulerServiceTaskFunction {\n return async abort => {\n const labels: Record<string, string> = {\n taskId: task.id,\n scope,\n };\n this.counter.add(1, { ...labels, result: 'started' });\n this.lastStarted.record(Date.now() / 1000, { taskId: task.id });\n\n const startTime = process.hrtime();\n\n try {\n await tracer.startActiveSpan(`task ${task.id}`, async span => {\n try {\n span.setAttributes(labels);\n await task.fn(abort);\n } catch (error) {\n if (error instanceof Error) {\n span.recordException(error);\n }\n throw error;\n } finally {\n span.end();\n }\n });\n labels.result = 'completed';\n } catch (ex) {\n labels.result = 'failed';\n throw ex;\n } finally {\n const delta = process.hrtime(startTime);\n const endTime = delta[0] + delta[1] / 1e9;\n this.counter.add(1, labels);\n this.duration.record(endTime, labels);\n this.lastCompleted.record(Date.now() / 1000, labels);\n }\n };\n }\n}\n\nexport function parseDuration(\n frequency: SchedulerServiceTaskScheduleDefinition['frequency'],\n): string {\n if (typeof frequency === 'object' && 'cron' in frequency) {\n return frequency.cron;\n }\n if (typeof frequency === 'object' && 'trigger' in frequency) {\n return frequency.trigger;\n }\n\n const parsed = Duration.isDuration(frequency)\n ? frequency\n : Duration.fromObject(frequency);\n\n if (!parsed.isValid) {\n throw new Error(\n `Invalid duration, ${parsed.invalidReason}: ${parsed.invalidExplanation}`,\n );\n }\n\n return parsed.toISO()!;\n}\n"],"names":["trace","TRACER_ID","metrics","TaskWorker","validateId","delegateAbortController","LocalTaskWorker","Router","Duration"],"mappings":"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"}
1
+ {"version":3,"file":"PluginTaskSchedulerImpl.cjs.js","sources":["../../../../src/entrypoints/scheduler/lib/PluginTaskSchedulerImpl.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport {\n LoggerService,\n RootLifecycleService,\n SchedulerService,\n SchedulerServiceTaskDescriptor,\n SchedulerServiceTaskFunction,\n SchedulerServiceTaskInvocationDefinition,\n SchedulerServiceTaskRunner,\n SchedulerServiceTaskScheduleDefinition,\n} from '@backstage/backend-plugin-api';\nimport { Counter, Histogram, Gauge, metrics, trace } from '@opentelemetry/api';\nimport { Knex } from 'knex';\nimport { Duration } from 'luxon';\nimport express from 'express';\nimport Router from 'express-promise-router';\nimport { LocalTaskWorker } from './LocalTaskWorker';\nimport { TaskWorker } from './TaskWorker';\nimport { TaskSettingsV2, TaskApiTasksResponse } from './types';\nimport { delegateAbortController, TRACER_ID, validateId } from './util';\n\nconst tracer = trace.getTracer(TRACER_ID);\n\n/**\n * Implements the actual task management.\n */\nexport class PluginTaskSchedulerImpl implements SchedulerService {\n private readonly localWorkersById = new Map<string, LocalTaskWorker>();\n private readonly globalWorkersById = new Map<string, TaskWorker>();\n private readonly allScheduledTasks: SchedulerServiceTaskDescriptor[] = [];\n private readonly shutdownInitiated: Promise<boolean>;\n\n private readonly counter: Counter;\n private readonly duration: Histogram;\n private readonly lastStarted: Gauge;\n private readonly lastCompleted: Gauge;\n\n private readonly pluginId: string;\n private readonly databaseFactory: () => Promise<Knex>;\n private readonly logger: LoggerService;\n\n constructor(\n pluginId: string,\n databaseFactory: () => Promise<Knex>,\n logger: LoggerService,\n rootLifecycle: RootLifecycleService,\n ) {\n this.pluginId = pluginId;\n this.databaseFactory = databaseFactory;\n this.logger = logger;\n const meter = metrics.getMeter('default');\n this.counter = meter.createCounter('backend_tasks.task.runs.count', {\n description: 'Total number of times a task has been run',\n });\n this.duration = meter.createHistogram('backend_tasks.task.runs.duration', {\n description: 'Histogram of task run durations',\n unit: 'seconds',\n });\n this.lastStarted = meter.createGauge('backend_tasks.task.runs.started', {\n description: 'Epoch timestamp seconds when the task was last started',\n unit: 'seconds',\n });\n this.lastCompleted = meter.createGauge(\n 'backend_tasks.task.runs.completed',\n {\n description: 'Epoch timestamp seconds when the task was last completed',\n unit: 'seconds',\n },\n );\n this.shutdownInitiated = new Promise(shutdownInitiated => {\n rootLifecycle.addShutdownHook(() => shutdownInitiated(true));\n });\n }\n\n async triggerTask(id: string): Promise<void> {\n const localTask = this.localWorkersById.get(id);\n if (localTask) {\n localTask.trigger();\n return;\n }\n\n const knex = await this.databaseFactory();\n await TaskWorker.trigger(knex, id);\n }\n\n async scheduleTask(\n task: SchedulerServiceTaskScheduleDefinition &\n SchedulerServiceTaskInvocationDefinition,\n ): Promise<void> {\n validateId(task.id);\n const scope = task.scope ?? 'global';\n\n const settings: TaskSettingsV2 = {\n version: 2,\n cadence: parseDuration(task.frequency),\n initialDelayDuration:\n task.initialDelay && parseDuration(task.initialDelay),\n timeoutAfterDuration: parseDuration(task.timeout),\n };\n\n // Delegated abort controller that will abort either when the provided\n // controller aborts, or when a root lifecycle shutdown happens\n const abortController = delegateAbortController(task.signal);\n this.shutdownInitiated.then(() => abortController.abort());\n\n if (scope === 'global') {\n const knex = await this.databaseFactory();\n const worker = new TaskWorker(\n task.id,\n this.instrumentedFunction(task, scope),\n knex,\n this.logger.child({ task: task.id }),\n );\n await worker.start(settings, { signal: abortController.signal });\n this.globalWorkersById.set(task.id, worker);\n } else {\n const worker = new LocalTaskWorker(\n task.id,\n this.instrumentedFunction(task, scope),\n this.logger.child({ task: task.id }),\n );\n worker.start(settings, { signal: abortController.signal });\n this.localWorkersById.set(task.id, worker);\n }\n\n this.allScheduledTasks.push({\n id: task.id,\n scope: scope,\n settings: settings,\n });\n }\n\n createScheduledTaskRunner(\n schedule: SchedulerServiceTaskScheduleDefinition,\n ): SchedulerServiceTaskRunner {\n return {\n run: async task => {\n await this.scheduleTask({ ...task, ...schedule });\n },\n };\n }\n\n async getScheduledTasks(): Promise<SchedulerServiceTaskDescriptor[]> {\n return this.allScheduledTasks;\n }\n\n getRouter(): express.Router {\n const router = Router();\n\n router.get('/.backstage/scheduler/v1/tasks', async (_, res) => {\n const globalState = await TaskWorker.taskStates(\n await this.databaseFactory(),\n );\n\n const tasks = new Array<TaskApiTasksResponse>();\n for (const task of this.allScheduledTasks) {\n tasks.push({\n taskId: task.id,\n pluginId: this.pluginId,\n scope: task.scope,\n settings: task.settings,\n taskState:\n this.localWorkersById.get(task.id)?.taskState() ??\n globalState.get(task.id) ??\n null,\n workerState:\n this.localWorkersById.get(task.id)?.workerState() ??\n this.globalWorkersById.get(task.id)?.workerState() ??\n null,\n });\n }\n\n res.json({ tasks });\n });\n\n router.post(\n '/.backstage/scheduler/v1/tasks/:id/trigger',\n async (req, res) => {\n const { id } = req.params;\n await this.triggerTask(id);\n res.status(200).end();\n },\n );\n\n return router;\n }\n\n private instrumentedFunction(\n task: SchedulerServiceTaskInvocationDefinition,\n scope: string,\n ): SchedulerServiceTaskFunction {\n return async abort => {\n const labels: Record<string, string> = {\n taskId: task.id,\n scope,\n };\n this.counter.add(1, { ...labels, result: 'started' });\n this.lastStarted.record(Date.now() / 1000, { taskId: task.id });\n\n const startTime = process.hrtime();\n\n try {\n await tracer.startActiveSpan(`task ${task.id}`, async span => {\n try {\n span.setAttributes(labels);\n await task.fn(abort);\n } catch (error) {\n if (error instanceof Error) {\n span.recordException(error);\n }\n throw error;\n } finally {\n span.end();\n }\n });\n labels.result = 'completed';\n } catch (ex) {\n labels.result = 'failed';\n throw ex;\n } finally {\n const delta = process.hrtime(startTime);\n const endTime = delta[0] + delta[1] / 1e9;\n this.counter.add(1, labels);\n this.duration.record(endTime, labels);\n this.lastCompleted.record(Date.now() / 1000, labels);\n }\n };\n }\n}\n\nexport function parseDuration(\n frequency: SchedulerServiceTaskScheduleDefinition['frequency'],\n): string {\n if (typeof frequency === 'object' && 'cron' in frequency) {\n return frequency.cron;\n }\n if (typeof frequency === 'object' && 'trigger' in frequency) {\n return frequency.trigger;\n }\n\n const parsed = Duration.isDuration(frequency)\n ? frequency\n : Duration.fromObject(frequency);\n\n if (!parsed.isValid) {\n throw new Error(\n `Invalid duration, ${parsed.invalidReason}: ${parsed.invalidExplanation}`,\n );\n }\n\n return parsed.toISO()!;\n}\n"],"names":["trace","TRACER_ID","metrics","TaskWorker","validateId","delegateAbortController","LocalTaskWorker","Router","Duration"],"mappings":"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"}
@@ -10,6 +10,14 @@ var util = require('./util.cjs.js');
10
10
 
11
11
  const DEFAULT_WORK_CHECK_FREQUENCY = luxon.Duration.fromObject({ seconds: 5 });
12
12
  class TaskWorker {
13
+ #workerState = {
14
+ status: "idle"
15
+ };
16
+ taskId;
17
+ fn;
18
+ knex;
19
+ logger;
20
+ workCheckFrequency;
13
21
  constructor(taskId, fn, knex, logger, workCheckFrequency = DEFAULT_WORK_CHECK_FREQUENCY) {
14
22
  this.taskId = taskId;
15
23
  this.fn = fn;
@@ -17,9 +25,6 @@ class TaskWorker {
17
25
  this.logger = logger;
18
26
  this.workCheckFrequency = workCheckFrequency;
19
27
  }
20
- #workerState = {
21
- status: "idle"
22
- };
23
28
  async start(settings, options) {
24
29
  try {
25
30
  await this.persistTask(settings);
@@ -27,7 +32,7 @@ class TaskWorker {
27
32
  throw new Error(`Failed to persist task, ${e}`);
28
33
  }
29
34
  this.logger.info(
30
- `Task worker starting: ${this.taskId}, ${JSON.stringify(settings)}`
35
+ `Registered scheduled task: ${this.taskId}, ${JSON.stringify(settings)}`
31
36
  );
32
37
  let workCheckFrequency = this.workCheckFrequency;
33
38
  const isDuration = settings?.cadence.startsWith("P");
@@ -1 +1 @@
1
- {"version":3,"file":"TaskWorker.cjs.js","sources":["../../../../src/entrypoints/scheduler/lib/TaskWorker.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { ConflictError, NotFoundError } from '@backstage/errors';\nimport { CronTime } from 'cron';\nimport { Knex } from 'knex';\nimport { DateTime, Duration } from 'luxon';\nimport { v4 as uuid } from 'uuid';\nimport { DB_TASKS_TABLE, DbTasksRow } from '../database/tables';\nimport {\n TaskSettingsV2,\n taskSettingsV2Schema,\n TaskApiTasksResponse,\n} from './types';\nimport {\n delegateAbortController,\n nowPlus,\n sleep,\n dbTime,\n serializeError,\n} from './util';\nimport { SchedulerServiceTaskFunction } from '@backstage/backend-plugin-api';\n\nconst DEFAULT_WORK_CHECK_FREQUENCY = Duration.fromObject({ seconds: 5 });\n\n/**\n * Implements tasks that run across worker hosts, with collaborative locking.\n *\n * @private\n */\nexport class TaskWorker {\n #workerState: TaskApiTasksResponse['workerState'] = {\n status: 'idle',\n };\n\n constructor(\n private readonly taskId: string,\n private readonly fn: SchedulerServiceTaskFunction,\n private readonly knex: Knex,\n private readonly logger: LoggerService,\n private readonly workCheckFrequency: Duration = DEFAULT_WORK_CHECK_FREQUENCY,\n ) {}\n\n async start(settings: TaskSettingsV2, options: { signal: AbortSignal }) {\n try {\n await this.persistTask(settings);\n } catch (e) {\n throw new Error(`Failed to persist task, ${e}`);\n }\n\n this.logger.info(\n `Task worker starting: ${this.taskId}, ${JSON.stringify(settings)}`,\n );\n\n let workCheckFrequency = this.workCheckFrequency;\n const isDuration = settings?.cadence.startsWith('P');\n if (isDuration) {\n const cadence = Duration.fromISO(settings.cadence);\n if (cadence < workCheckFrequency) {\n workCheckFrequency = cadence;\n }\n }\n\n (async () => {\n let attemptNum = 1;\n for (;;) {\n try {\n await this.performInitialWait(settings, options.signal);\n\n while (!options.signal.aborted) {\n const runResult = await this.runOnce(options.signal);\n if (runResult.result === 'abort') {\n break;\n }\n await sleep(workCheckFrequency, options.signal);\n }\n\n this.logger.info(`Task worker finished: ${this.taskId}`);\n attemptNum = 0;\n break;\n } catch (e) {\n attemptNum += 1;\n this.logger.warn(\n `Task worker failed unexpectedly, attempt number ${attemptNum}, ${e}`,\n );\n await sleep(Duration.fromObject({ seconds: 1 }));\n }\n }\n })();\n }\n\n /**\n * Does the once-at-startup initial wait, if configured.\n */\n private async performInitialWait(\n settings: TaskSettingsV2,\n signal: AbortSignal,\n ): Promise<void> {\n if (settings.initialDelayDuration) {\n this.#workerState = {\n status: 'initial-wait',\n };\n await sleep(Duration.fromISO(settings.initialDelayDuration), signal);\n }\n this.#workerState = {\n status: 'idle',\n };\n }\n\n static async trigger(knex: Knex, taskId: string): Promise<void> {\n // check if task exists\n const rows = await knex<DbTasksRow>(DB_TASKS_TABLE)\n .select(knex.raw(1))\n .where('id', '=', taskId);\n if (rows.length !== 1) {\n throw new NotFoundError(`Task ${taskId} does not exist`);\n }\n\n const updatedRows = await knex<DbTasksRow>(DB_TASKS_TABLE)\n .where('id', '=', taskId)\n .whereNull('current_run_ticket')\n .update({\n next_run_start_at: knex.fn.now(),\n });\n if (updatedRows < 1) {\n throw new ConflictError(`Task ${taskId} is currently running`);\n }\n }\n\n static async taskStates(\n knex: Knex,\n ): Promise<Map<string, TaskApiTasksResponse['taskState']>> {\n const rows = await knex<DbTasksRow>(DB_TASKS_TABLE);\n return new Map(\n rows.map(row => {\n const startedAt = row.current_run_started_at\n ? dbTime(row.current_run_started_at).toISO()!\n : undefined;\n const timesOutAt = row.current_run_expires_at\n ? dbTime(row.current_run_expires_at).toISO()!\n : undefined;\n const startsAt = row.next_run_start_at\n ? dbTime(row.next_run_start_at).toISO()!\n : undefined;\n const lastRunEndedAt = row.last_run_ended_at\n ? dbTime(row.last_run_ended_at).toISO()!\n : undefined;\n const lastRunError = row.last_run_error_json || undefined;\n\n return [\n row.id,\n startedAt\n ? {\n status: 'running',\n startedAt,\n timesOutAt,\n lastRunEndedAt,\n lastRunError,\n }\n : {\n status: 'idle',\n startsAt,\n lastRunEndedAt,\n lastRunError,\n },\n ];\n }),\n );\n }\n\n workerState(): TaskApiTasksResponse['workerState'] {\n return this.#workerState;\n }\n\n /**\n * Makes a single attempt at running the task to completion, if ready.\n *\n * @returns The outcome of the attempt\n */\n private async runOnce(\n signal: AbortSignal,\n ): Promise<\n | { result: 'not-ready-yet' }\n | { result: 'abort' }\n | { result: 'failed' }\n | { result: 'completed' }\n > {\n const findResult = await this.findReadyTask();\n if (\n findResult.result === 'not-ready-yet' ||\n findResult.result === 'abort'\n ) {\n return findResult;\n }\n\n const taskSettings = findResult.settings;\n const ticket = uuid();\n\n const claimed = await this.tryClaimTask(ticket, taskSettings);\n if (!claimed) {\n return { result: 'not-ready-yet' };\n }\n\n // Abort the task execution either if the worker is stopped, or if the\n // task timeout is hit\n const taskAbortController = delegateAbortController(signal);\n const timeoutHandle = setTimeout(() => {\n taskAbortController.abort();\n }, Duration.fromISO(taskSettings.timeoutAfterDuration).as('milliseconds'));\n\n try {\n this.#workerState = {\n status: 'running',\n };\n await this.fn(taskAbortController.signal);\n taskAbortController.abort(); // releases resources\n } catch (e) {\n this.logger.error(e);\n await this.tryReleaseTask(ticket, taskSettings, e);\n return { result: 'failed' };\n } finally {\n this.#workerState = {\n status: 'idle',\n };\n clearTimeout(timeoutHandle);\n }\n\n await this.tryReleaseTask(ticket, taskSettings);\n return { result: 'completed' };\n }\n\n /**\n * Perform the initial store of the task info\n */\n async persistTask(settings: TaskSettingsV2) {\n // Perform an initial parse to ensure that we will definitely be able to\n // read it back again.\n taskSettingsV2Schema.parse(settings);\n\n const isManual = settings?.cadence === 'manual';\n const isDuration = settings?.cadence.startsWith('P');\n const isCron = !isManual && !isDuration;\n\n let startAt: Knex.Raw | undefined;\n let nextStartAt: Knex.Raw | undefined;\n if (settings.initialDelayDuration) {\n startAt = nowPlus(\n Duration.fromISO(settings.initialDelayDuration),\n this.knex,\n );\n }\n\n if (isCron) {\n const time = new CronTime(settings.cadence)\n .sendAt()\n .minus({ seconds: 1 }) // immediately, if \"* * * * * *\"\n .toUTC();\n\n nextStartAt = this.nextRunAtRaw(time);\n startAt ||= nextStartAt;\n } else if (isManual) {\n nextStartAt = this.knex.raw('null');\n startAt ||= nextStartAt;\n } else {\n startAt ||= this.knex.fn.now();\n nextStartAt = nowPlus(Duration.fromISO(settings.cadence), this.knex);\n }\n\n this.logger.debug(`task: ${this.taskId} configured to run at: ${startAt}`);\n\n // It's OK if the task already exists; if it does, just replace its\n // settings with the new value and start the loop as usual.\n const settingsJson = JSON.stringify(settings);\n await this.knex<DbTasksRow>(DB_TASKS_TABLE)\n .insert({\n id: this.taskId,\n settings_json: settingsJson,\n next_run_start_at: startAt,\n })\n .onConflict('id')\n .merge(\n this.knex.client.config.client.includes('mysql')\n ? {\n settings_json: settingsJson,\n next_run_start_at: this.knex.raw(\n `CASE WHEN ?? < ?? THEN ?? ELSE ?? END`,\n [\n nextStartAt,\n 'next_run_start_at',\n nextStartAt,\n 'next_run_start_at',\n ],\n ),\n }\n : {\n settings_json: this.knex.ref('excluded.settings_json'),\n next_run_start_at: this.knex.raw(\n `CASE WHEN ?? < ?? THEN ?? ELSE ?? END`,\n [\n nextStartAt,\n `${DB_TASKS_TABLE}.next_run_start_at`,\n nextStartAt,\n `${DB_TASKS_TABLE}.next_run_start_at`,\n ],\n ),\n },\n );\n }\n\n /**\n * Check if the task is ready to run\n */\n async findReadyTask(): Promise<\n | { result: 'not-ready-yet' }\n | { result: 'abort' }\n | { result: 'ready'; settings: TaskSettingsV2 }\n > {\n const [row] = await this.knex<DbTasksRow>(DB_TASKS_TABLE)\n .where('id', '=', this.taskId)\n .select({\n settingsJson: 'settings_json',\n ready: this.knex.raw(\n `CASE\n WHEN next_run_start_at <= ? AND current_run_ticket IS NULL THEN TRUE\n ELSE FALSE\n END`,\n [this.knex.fn.now()],\n ),\n });\n\n if (!row) {\n this.logger.info(\n 'No longer able to find task; aborting and assuming that it has been unregistered or expired',\n );\n return { result: 'abort' };\n } else if (!row.ready) {\n return { result: 'not-ready-yet' };\n }\n\n try {\n const obj = JSON.parse(row.settingsJson);\n const settings = taskSettingsV2Schema.parse(obj);\n return { result: 'ready', settings };\n } catch (e) {\n this.logger.info(\n `Task \"${this.taskId}\" is no longer able to parse task settings; aborting and assuming that a ` +\n `newer version of the task has been issued and being handled by other workers, ${e}`,\n );\n return { result: 'abort' };\n }\n }\n\n /**\n * Attempts to claim a task that's ready for execution, on this worker's\n * behalf. We should not attempt to perform the work unless the claim really\n * goes through.\n *\n * @param ticket - A globally unique string that changes for each invocation\n * @param settings - The settings of the task to claim\n * @returns True if it was successfully claimed\n */\n async tryClaimTask(\n ticket: string,\n settings: TaskSettingsV2,\n ): Promise<boolean> {\n const startedAt = this.knex.fn.now();\n const expiresAt = settings.timeoutAfterDuration\n ? nowPlus(Duration.fromISO(settings.timeoutAfterDuration), this.knex)\n : this.knex.raw('null');\n\n const rows = await this.knex<DbTasksRow>(DB_TASKS_TABLE)\n .where('id', '=', this.taskId)\n .whereNull('current_run_ticket')\n .update({\n current_run_ticket: ticket,\n current_run_started_at: startedAt,\n current_run_expires_at: expiresAt,\n });\n\n return rows === 1;\n }\n\n async tryReleaseTask(\n ticket: string,\n settings: TaskSettingsV2,\n error?: Error,\n ): Promise<boolean> {\n const isManual = settings?.cadence === 'manual';\n const isDuration = settings?.cadence.startsWith('P');\n const isCron = !isManual && !isDuration;\n\n let nextRun: Knex.Raw;\n if (isCron) {\n const time = new CronTime(settings.cadence).sendAt().toUTC();\n this.logger.debug(`task: ${this.taskId} will next occur around ${time}`);\n\n nextRun = this.nextRunAtRaw(time);\n } else if (isManual) {\n nextRun = this.knex.raw('null');\n } else {\n const dt = Duration.fromISO(settings.cadence).as('seconds');\n this.logger.debug(\n `task: ${this.taskId} will next occur around ${DateTime.now().plus({\n seconds: dt,\n })}`,\n );\n\n if (this.knex.client.config.client.includes('sqlite3')) {\n nextRun = this.knex.raw(\n `max(datetime(next_run_start_at, ?), datetime('now'))`,\n [`+${dt} seconds`],\n );\n } else if (this.knex.client.config.client.includes('mysql')) {\n nextRun = this.knex.raw(\n `greatest(next_run_start_at + interval ${dt} second, now())`,\n );\n } else {\n nextRun = this.knex.raw(\n `greatest(next_run_start_at + interval '${dt} seconds', now())`,\n );\n }\n }\n\n const rows = await this.knex<DbTasksRow>(DB_TASKS_TABLE)\n .where('id', '=', this.taskId)\n .where('current_run_ticket', '=', ticket)\n .update({\n next_run_start_at: nextRun,\n current_run_ticket: this.knex.raw('null'),\n current_run_started_at: this.knex.raw('null'),\n current_run_expires_at: this.knex.raw('null'),\n last_run_ended_at: this.knex.fn.now(),\n last_run_error_json: error\n ? serializeError(error)\n : this.knex.raw('null'),\n });\n\n return rows === 1;\n }\n\n private nextRunAtRaw(time: DateTime): Knex.Raw {\n if (this.knex.client.config.client.includes('sqlite3')) {\n return this.knex.raw('datetime(?)', [time.toISO()]);\n } else if (this.knex.client.config.client.includes('mysql')) {\n return this.knex.raw(`?`, [time.toSQL({ includeOffset: false })]);\n }\n return this.knex.raw(`?`, [time.toISO()]);\n }\n}\n"],"names":["Duration","sleep","DB_TASKS_TABLE","NotFoundError","ConflictError","dbTime","uuid","delegateAbortController","taskSettingsV2Schema","nowPlus","CronTime","DateTime","serializeError"],"mappings":"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"}
1
+ {"version":3,"file":"TaskWorker.cjs.js","sources":["../../../../src/entrypoints/scheduler/lib/TaskWorker.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { LoggerService } from '@backstage/backend-plugin-api';\nimport { ConflictError, NotFoundError } from '@backstage/errors';\nimport { CronTime } from 'cron';\nimport { Knex } from 'knex';\nimport { DateTime, Duration } from 'luxon';\nimport { v4 as uuid } from 'uuid';\nimport { DB_TASKS_TABLE, DbTasksRow } from '../database/tables';\nimport {\n TaskSettingsV2,\n taskSettingsV2Schema,\n TaskApiTasksResponse,\n} from './types';\nimport {\n delegateAbortController,\n nowPlus,\n sleep,\n dbTime,\n serializeError,\n} from './util';\nimport { SchedulerServiceTaskFunction } from '@backstage/backend-plugin-api';\n\nconst DEFAULT_WORK_CHECK_FREQUENCY = Duration.fromObject({ seconds: 5 });\n\n/**\n * Implements tasks that run across worker hosts, with collaborative locking.\n *\n * @private\n */\nexport class TaskWorker {\n #workerState: TaskApiTasksResponse['workerState'] = {\n status: 'idle',\n };\n private readonly taskId: string;\n private readonly fn: SchedulerServiceTaskFunction;\n private readonly knex: Knex;\n private readonly logger: LoggerService;\n private readonly workCheckFrequency: Duration;\n\n constructor(\n taskId: string,\n fn: SchedulerServiceTaskFunction,\n knex: Knex,\n logger: LoggerService,\n workCheckFrequency: Duration = DEFAULT_WORK_CHECK_FREQUENCY,\n ) {\n this.taskId = taskId;\n this.fn = fn;\n this.knex = knex;\n this.logger = logger;\n this.workCheckFrequency = workCheckFrequency;\n }\n\n async start(settings: TaskSettingsV2, options: { signal: AbortSignal }) {\n try {\n await this.persistTask(settings);\n } catch (e) {\n throw new Error(`Failed to persist task, ${e}`);\n }\n\n this.logger.info(\n `Registered scheduled task: ${this.taskId}, ${JSON.stringify(settings)}`,\n );\n\n let workCheckFrequency = this.workCheckFrequency;\n const isDuration = settings?.cadence.startsWith('P');\n if (isDuration) {\n const cadence = Duration.fromISO(settings.cadence);\n if (cadence < workCheckFrequency) {\n workCheckFrequency = cadence;\n }\n }\n\n (async () => {\n let attemptNum = 1;\n for (;;) {\n try {\n await this.performInitialWait(settings, options.signal);\n\n while (!options.signal.aborted) {\n const runResult = await this.runOnce(options.signal);\n if (runResult.result === 'abort') {\n break;\n }\n await sleep(workCheckFrequency, options.signal);\n }\n\n this.logger.info(`Task worker finished: ${this.taskId}`);\n attemptNum = 0;\n break;\n } catch (e) {\n attemptNum += 1;\n this.logger.warn(\n `Task worker failed unexpectedly, attempt number ${attemptNum}, ${e}`,\n );\n await sleep(Duration.fromObject({ seconds: 1 }));\n }\n }\n })();\n }\n\n /**\n * Does the once-at-startup initial wait, if configured.\n */\n private async performInitialWait(\n settings: TaskSettingsV2,\n signal: AbortSignal,\n ): Promise<void> {\n if (settings.initialDelayDuration) {\n this.#workerState = {\n status: 'initial-wait',\n };\n await sleep(Duration.fromISO(settings.initialDelayDuration), signal);\n }\n this.#workerState = {\n status: 'idle',\n };\n }\n\n static async trigger(knex: Knex, taskId: string): Promise<void> {\n // check if task exists\n const rows = await knex<DbTasksRow>(DB_TASKS_TABLE)\n .select(knex.raw(1))\n .where('id', '=', taskId);\n if (rows.length !== 1) {\n throw new NotFoundError(`Task ${taskId} does not exist`);\n }\n\n const updatedRows = await knex<DbTasksRow>(DB_TASKS_TABLE)\n .where('id', '=', taskId)\n .whereNull('current_run_ticket')\n .update({\n next_run_start_at: knex.fn.now(),\n });\n if (updatedRows < 1) {\n throw new ConflictError(`Task ${taskId} is currently running`);\n }\n }\n\n static async taskStates(\n knex: Knex,\n ): Promise<Map<string, TaskApiTasksResponse['taskState']>> {\n const rows = await knex<DbTasksRow>(DB_TASKS_TABLE);\n return new Map(\n rows.map(row => {\n const startedAt = row.current_run_started_at\n ? dbTime(row.current_run_started_at).toISO()!\n : undefined;\n const timesOutAt = row.current_run_expires_at\n ? dbTime(row.current_run_expires_at).toISO()!\n : undefined;\n const startsAt = row.next_run_start_at\n ? dbTime(row.next_run_start_at).toISO()!\n : undefined;\n const lastRunEndedAt = row.last_run_ended_at\n ? dbTime(row.last_run_ended_at).toISO()!\n : undefined;\n const lastRunError = row.last_run_error_json || undefined;\n\n return [\n row.id,\n startedAt\n ? {\n status: 'running',\n startedAt,\n timesOutAt,\n lastRunEndedAt,\n lastRunError,\n }\n : {\n status: 'idle',\n startsAt,\n lastRunEndedAt,\n lastRunError,\n },\n ];\n }),\n );\n }\n\n workerState(): TaskApiTasksResponse['workerState'] {\n return this.#workerState;\n }\n\n /**\n * Makes a single attempt at running the task to completion, if ready.\n *\n * @returns The outcome of the attempt\n */\n private async runOnce(\n signal: AbortSignal,\n ): Promise<\n | { result: 'not-ready-yet' }\n | { result: 'abort' }\n | { result: 'failed' }\n | { result: 'completed' }\n > {\n const findResult = await this.findReadyTask();\n if (\n findResult.result === 'not-ready-yet' ||\n findResult.result === 'abort'\n ) {\n return findResult;\n }\n\n const taskSettings = findResult.settings;\n const ticket = uuid();\n\n const claimed = await this.tryClaimTask(ticket, taskSettings);\n if (!claimed) {\n return { result: 'not-ready-yet' };\n }\n\n // Abort the task execution either if the worker is stopped, or if the\n // task timeout is hit\n const taskAbortController = delegateAbortController(signal);\n const timeoutHandle = setTimeout(() => {\n taskAbortController.abort();\n }, Duration.fromISO(taskSettings.timeoutAfterDuration).as('milliseconds'));\n\n try {\n this.#workerState = {\n status: 'running',\n };\n await this.fn(taskAbortController.signal);\n taskAbortController.abort(); // releases resources\n } catch (e) {\n this.logger.error(e);\n await this.tryReleaseTask(ticket, taskSettings, e);\n return { result: 'failed' };\n } finally {\n this.#workerState = {\n status: 'idle',\n };\n clearTimeout(timeoutHandle);\n }\n\n await this.tryReleaseTask(ticket, taskSettings);\n return { result: 'completed' };\n }\n\n /**\n * Perform the initial store of the task info\n */\n async persistTask(settings: TaskSettingsV2) {\n // Perform an initial parse to ensure that we will definitely be able to\n // read it back again.\n taskSettingsV2Schema.parse(settings);\n\n const isManual = settings?.cadence === 'manual';\n const isDuration = settings?.cadence.startsWith('P');\n const isCron = !isManual && !isDuration;\n\n let startAt: Knex.Raw | undefined;\n let nextStartAt: Knex.Raw | undefined;\n if (settings.initialDelayDuration) {\n startAt = nowPlus(\n Duration.fromISO(settings.initialDelayDuration),\n this.knex,\n );\n }\n\n if (isCron) {\n const time = new CronTime(settings.cadence)\n .sendAt()\n .minus({ seconds: 1 }) // immediately, if \"* * * * * *\"\n .toUTC();\n\n nextStartAt = this.nextRunAtRaw(time);\n startAt ||= nextStartAt;\n } else if (isManual) {\n nextStartAt = this.knex.raw('null');\n startAt ||= nextStartAt;\n } else {\n startAt ||= this.knex.fn.now();\n nextStartAt = nowPlus(Duration.fromISO(settings.cadence), this.knex);\n }\n\n this.logger.debug(`task: ${this.taskId} configured to run at: ${startAt}`);\n\n // It's OK if the task already exists; if it does, just replace its\n // settings with the new value and start the loop as usual.\n const settingsJson = JSON.stringify(settings);\n await this.knex<DbTasksRow>(DB_TASKS_TABLE)\n .insert({\n id: this.taskId,\n settings_json: settingsJson,\n next_run_start_at: startAt,\n })\n .onConflict('id')\n .merge(\n this.knex.client.config.client.includes('mysql')\n ? {\n settings_json: settingsJson,\n next_run_start_at: this.knex.raw(\n `CASE WHEN ?? < ?? THEN ?? ELSE ?? END`,\n [\n nextStartAt,\n 'next_run_start_at',\n nextStartAt,\n 'next_run_start_at',\n ],\n ),\n }\n : {\n settings_json: this.knex.ref('excluded.settings_json'),\n next_run_start_at: this.knex.raw(\n `CASE WHEN ?? < ?? THEN ?? ELSE ?? END`,\n [\n nextStartAt,\n `${DB_TASKS_TABLE}.next_run_start_at`,\n nextStartAt,\n `${DB_TASKS_TABLE}.next_run_start_at`,\n ],\n ),\n },\n );\n }\n\n /**\n * Check if the task is ready to run\n */\n async findReadyTask(): Promise<\n | { result: 'not-ready-yet' }\n | { result: 'abort' }\n | { result: 'ready'; settings: TaskSettingsV2 }\n > {\n const [row] = await this.knex<DbTasksRow>(DB_TASKS_TABLE)\n .where('id', '=', this.taskId)\n .select({\n settingsJson: 'settings_json',\n ready: this.knex.raw(\n `CASE\n WHEN next_run_start_at <= ? AND current_run_ticket IS NULL THEN TRUE\n ELSE FALSE\n END`,\n [this.knex.fn.now()],\n ),\n });\n\n if (!row) {\n this.logger.info(\n 'No longer able to find task; aborting and assuming that it has been unregistered or expired',\n );\n return { result: 'abort' };\n } else if (!row.ready) {\n return { result: 'not-ready-yet' };\n }\n\n try {\n const obj = JSON.parse(row.settingsJson);\n const settings = taskSettingsV2Schema.parse(obj);\n return { result: 'ready', settings };\n } catch (e) {\n this.logger.info(\n `Task \"${this.taskId}\" is no longer able to parse task settings; aborting and assuming that a ` +\n `newer version of the task has been issued and being handled by other workers, ${e}`,\n );\n return { result: 'abort' };\n }\n }\n\n /**\n * Attempts to claim a task that's ready for execution, on this worker's\n * behalf. We should not attempt to perform the work unless the claim really\n * goes through.\n *\n * @param ticket - A globally unique string that changes for each invocation\n * @param settings - The settings of the task to claim\n * @returns True if it was successfully claimed\n */\n async tryClaimTask(\n ticket: string,\n settings: TaskSettingsV2,\n ): Promise<boolean> {\n const startedAt = this.knex.fn.now();\n const expiresAt = settings.timeoutAfterDuration\n ? nowPlus(Duration.fromISO(settings.timeoutAfterDuration), this.knex)\n : this.knex.raw('null');\n\n const rows = await this.knex<DbTasksRow>(DB_TASKS_TABLE)\n .where('id', '=', this.taskId)\n .whereNull('current_run_ticket')\n .update({\n current_run_ticket: ticket,\n current_run_started_at: startedAt,\n current_run_expires_at: expiresAt,\n });\n\n return rows === 1;\n }\n\n async tryReleaseTask(\n ticket: string,\n settings: TaskSettingsV2,\n error?: Error,\n ): Promise<boolean> {\n const isManual = settings?.cadence === 'manual';\n const isDuration = settings?.cadence.startsWith('P');\n const isCron = !isManual && !isDuration;\n\n let nextRun: Knex.Raw;\n if (isCron) {\n const time = new CronTime(settings.cadence).sendAt().toUTC();\n this.logger.debug(`task: ${this.taskId} will next occur around ${time}`);\n\n nextRun = this.nextRunAtRaw(time);\n } else if (isManual) {\n nextRun = this.knex.raw('null');\n } else {\n const dt = Duration.fromISO(settings.cadence).as('seconds');\n this.logger.debug(\n `task: ${this.taskId} will next occur around ${DateTime.now().plus({\n seconds: dt,\n })}`,\n );\n\n if (this.knex.client.config.client.includes('sqlite3')) {\n nextRun = this.knex.raw(\n `max(datetime(next_run_start_at, ?), datetime('now'))`,\n [`+${dt} seconds`],\n );\n } else if (this.knex.client.config.client.includes('mysql')) {\n nextRun = this.knex.raw(\n `greatest(next_run_start_at + interval ${dt} second, now())`,\n );\n } else {\n nextRun = this.knex.raw(\n `greatest(next_run_start_at + interval '${dt} seconds', now())`,\n );\n }\n }\n\n const rows = await this.knex<DbTasksRow>(DB_TASKS_TABLE)\n .where('id', '=', this.taskId)\n .where('current_run_ticket', '=', ticket)\n .update({\n next_run_start_at: nextRun,\n current_run_ticket: this.knex.raw('null'),\n current_run_started_at: this.knex.raw('null'),\n current_run_expires_at: this.knex.raw('null'),\n last_run_ended_at: this.knex.fn.now(),\n last_run_error_json: error\n ? serializeError(error)\n : this.knex.raw('null'),\n });\n\n return rows === 1;\n }\n\n private nextRunAtRaw(time: DateTime): Knex.Raw {\n if (this.knex.client.config.client.includes('sqlite3')) {\n return this.knex.raw('datetime(?)', [time.toISO()]);\n } else if (this.knex.client.config.client.includes('mysql')) {\n return this.knex.raw(`?`, [time.toSQL({ includeOffset: false })]);\n }\n return this.knex.raw(`?`, [time.toISO()]);\n }\n}\n"],"names":["Duration","sleep","DB_TASKS_TABLE","NotFoundError","ConflictError","dbTime","uuid","delegateAbortController","taskSettingsV2Schema","nowPlus","CronTime","DateTime","serializeError"],"mappings":"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"}