diff --git a/deno.json b/deno.json index af7f8b9..6a38108 100644 --- a/deno.json +++ b/deno.json @@ -5,7 +5,7 @@ } }, "tasks": { - "start": "deno run --allow-all src/index.ts" + "start": "config=develop deno run --allow-all example/config/main.ts" }, "lint": { "files": { diff --git a/deno.lock b/deno.lock index 4c54405..05a8474 100644 --- a/deno.lock +++ b/deno.lock @@ -1,5 +1,28 @@ { - "version": "2", + "version": "3", + "packages": { + "specifiers": { + "npm:superjson@1.13.3": "npm:superjson@1.13.3" + }, + "npm": { + "copy-anything@3.0.5": { + "integrity": "sha512-yCEafptTtb4bk7GLEQoM8KVJpxAfdBJYaXyzQEgQQQgYrZiDp8SJmGKlYza6CYjEDNstAdNdKA3UuoULlEbS6w==", + "dependencies": { + "is-what": "is-what@4.1.16" + } + }, + "is-what@4.1.16": { + "integrity": "sha512-ZhMwEosbFJkA0YhFnNDgTM4ZxDRsS6HqTo7qsZM08fehyRYIYa0yHu5R6mgo1n/8MgaPBXiPimPD77baVFYg+A==", + "dependencies": {} + }, + "superjson@1.13.3": { + "integrity": "sha512-mJiVjfd2vokfDxsQPOwJ/PtanO87LhpYY88ubI5dUB1Ab58Txbyje3+jpm+/83R/fevaq/107NNhtYBLuoTrFg==", + "dependencies": { + "copy-anything": "copy-anything@3.0.5" + } + } + } + }, "remote": { "http://localhost:8000/drafts/draft1/envFun.ts": "0ed4eae8c955d45346f0ec41a630aa4632fbcad39b86d29b0754fbad49708b95", "http://localhost:8000/drafts/draft1/error.ts": "728f1a38badc0917b524c91e5cf1e7014152804741865097d400078fe7b2115c", @@ -136,6 +159,19 @@ "https://deno.land/std@0.195.0/path/_interface.ts": "6471159dfbbc357e03882c2266d21ef9afdb1e4aa771b0545e90db58a0ba314b", "https://deno.land/std@0.195.0/path/_util.ts": "d7abb1e0dea065f427b89156e28cdeb32b045870acdf865833ba808a73b576d0", "https://deno.land/std@0.195.0/path/posix.ts": "8b7c67ac338714b30c816079303d0285dd24af6b284f7ad63da5b27372a2c94d", + "https://deno.land/std@0.203.0/dotenv/load.ts": "0636983549b98f29ab75c9a22a42d9723f0a389ece5498fe971e7bb2556a12e2", + "https://deno.land/std@0.203.0/dotenv/mod.ts": "1da8c6d0e7f7d8a5c2b19400b763bc11739df24acec235dda7ea2cfd3d300057", + "https://deno.land/std@0.90.0/_util/assert.ts": "2f868145a042a11d5ad0a3c748dcf580add8a0dbc0e876eaa0026303a5488f58", + "https://deno.land/std@0.90.0/async/deferred.ts": "f89ed49ba5e1dd0227c6bd5b23f017be46c3f92e4f0338dda08ff5aa54b9f6c9", + "https://deno.land/std@0.90.0/async/delay.ts": "9de1d8d07d1927767ab7f82434b883f3d8294fb19cad819691a2ad81a728cf3d", + "https://deno.land/std@0.90.0/async/mod.ts": "253b41c658d768613eacfb11caa0a9ca7148442f932018a45576f7f27554c853", + "https://deno.land/std@0.90.0/async/mux_async_iterator.ts": "b9091909db04cdb0af6f7807677372f64c1488de6c4bd86004511b064bf230d6", + "https://deno.land/std@0.90.0/async/pool.ts": "876f9e6815366cd017a3b4fbb9e9ae40310b1b6972f1bd541c94358bc11fb7e5", + "https://deno.land/std@0.90.0/fmt/colors.ts": "db22b314a2ae9430ae7460ce005e0a7130e23ae1c999157e3bb77cf55800f7e4", + "https://deno.land/std@0.90.0/node/_utils.ts": "067c386d676432e9418808851e8de72df7774f009a652904f62358b4c94504cf", + "https://deno.land/std@0.90.0/node/events.ts": "0feda0707e2229363f5df8b799fed41bb91de6ca7106f27c7a9f0a02ea11b9d4", + "https://deno.land/std@0.90.0/testing/_diff.ts": "961eaf6d9f5b0a8556c9d835bbc6fa74f5addd7d3b02728ba7936ff93364f7a3", + "https://deno.land/std@0.90.0/testing/asserts.ts": "83889f9a37bdab16cb68b023a15f9172ceb644f62c0e727c72d4870a666e53d6", "https://deno.land/x/axiod@0.26.2/helpers.ts": "467f2ca75608f368c8092e800eab0d6d79b3fa42346372be62a5b93acf50fe7e", "https://deno.land/x/axiod@0.26.2/interfaces.ts": "1b5a7b70b1e7faecd2f251ad080bd87188fba585e2de667af472c4d72abf636e", "https://deno.land/x/axiod@0.26.2/mod.ts": "1175ec90a040d764b9940753f8d8e3f37a2328a0536eed48e411a8f1a3e9e5cb", @@ -143,6 +179,19 @@ "https://deno.land/x/dotenv@v3.2.2/mod.ts": "077b48773de9205266a0b44c3c3a3c3083449ed64bb0b6cc461b95720678d38e", "https://deno.land/x/dotenv@v3.2.2/util.ts": "693730877b13f8ead2b79b2aa31e2a0652862f7dc0c5f6d2f313f4d39c7b7670", "https://deno.land/x/exec@0.0.5/mod.ts": "2a71f7e23e25be883275b22d872bbd2c3dfa3058934f1f156c8663fb81f5894f", + "https://deno.land/x/integrations@v0.0.6/notion/mod.ts": "e7caa2977944c92a2ead5f361332d274d5243171aadd888ea03c141245163a73", + "https://deno.land/x/integrations@v0.0.6/notion/src/appendors.ts": "1e175e45b15b3954eaea7462824f493747a9eae155b4175b6d8656e9575d406c", + "https://deno.land/x/integrations@v0.0.6/notion/src/behaviors.ts": "6c7be226f25855f43784c83d99778295e516a0da94a0a73c04d4e9453236f710", + "https://deno.land/x/integrations@v0.0.6/notion/src/blockInterfaces.ts": "18b2fd523c9c2d90109f3d0f1857ca353892f0d081af4e173c11b89b53d121c4", + "https://deno.land/x/integrations@v0.0.6/notion/src/errors/commonErrors.ts": "d6d385a0166d5626112e81e3f61dee6a28e545f70449d7a1632667ebf6a530b1", + "https://deno.land/x/integrations@v0.0.6/notion/src/errors/fetchErrors.ts": "0c7f542ef1475783481191011a2f1fec9f1a32d846b61993b5d5fba6ef5f798e", + "https://deno.land/x/integrations@v0.0.6/notion/src/errors/formatErrors.ts": "47432dd353fc11149fc409cf1be8fdf61d40c96c9319ffb2467be6205ae5f396", + "https://deno.land/x/integrations@v0.0.6/notion/src/errors/mod.ts": "a29c4f6d35aea45b3a0e59bfa747bcd3333319d36226d9782b6fbc1bba705041", + "https://deno.land/x/integrations@v0.0.6/notion/src/extractors.ts": "683e0348569a9dfb58d1333471af70e4513cdf90d2c3c9032c1d8dd123291704", + "https://deno.land/x/integrations@v0.0.6/notion/src/getters.ts": "f2408a1c5e8e77a2ad6517707c3a45a08f6832bde4eba8b8f07692a1e99aef30", + "https://deno.land/x/integrations@v0.0.6/notion/src/helpers.ts": "c4504e7c426c4663a35866ac9ec25980bacb50a40b6301e7ba411032dfe241f8", + "https://deno.land/x/integrations@v0.0.6/notion/src/mod.ts": "49979e7e9e09dee17f3b420940477acc87dc1e61a7b5477c2e445be154e1e3db", + "https://deno.land/x/integrations@v0.0.6/notion/src/notion.ts": "51f6cc9639e33fb93be7ed181e186ca6a232cd7e230f6ee5e744756544224636", "https://deno.land/x/json_tree@latest/mod.ts": "f635eeff567dd4050944294e3297dbc59a370acb6fb6e065ebb5a03516a40942", "https://deno.land/x/notion_sdk@v2.2.3/src/Client.ts": "00938a998e9aa825688979e6b51669f12387ff9800477dda255ca325a7868de8", "https://deno.land/x/notion_sdk@v2.2.3/src/api-endpoints.ts": "2390b0e86be564a00e1b97f2cf2877f5ffbe09221208cf93f865ce45ef71b27b", @@ -152,6 +201,18 @@ "https://deno.land/x/notion_sdk@v2.2.3/src/mod.ts": "fd42505b28c4dcc791369f2a070a4871799aa86bbfdb39f60745dd7746a4e684", "https://deno.land/x/notion_sdk@v2.2.3/src/type-utils.ts": "f533961a11ca45b4362de4688087104da18c37f5fc081c2d388112891287f033", "https://deno.land/x/notion_sdk@v2.2.3/src/utils.ts": "e47eefcad19c6b4ce4c5ee7f34e9201edf4ba7af5f9e3fababc3253e0bb637cd", + "https://deno.land/x/tuner@v0.1.4/mod.ts": "507c0d48fbc6dede825ce982e552f165a8e3691f5fed1b53d08b034aeb62ea41", + "https://deno.land/x/tuner@v0.1.4/src/envFuns.ts": "eb4cf72ca58c9f0b5389dfff7261882bd44d126af6d3e1900ea2f7b9c3b7a972", + "https://deno.land/x/tuner@v0.1.4/src/errors.ts": "35c4d23e6092b406300de3069a18588c22f52fac9a23d67a09e3468d384fcf08", + "https://deno.land/x/tuner@v0.1.4/src/loaders.ts": "18ab067131f5840543a2b018d31dd166530b9f673fa21fe61a448828a16d73fc", + "https://deno.land/x/tuner@v0.1.4/src/pathHelper.ts": "6e5ce0e937dc3007adca1334dc9e5662ea6c287815ca1fd039ecc75fc03b3d6d", + "https://deno.land/x/tuner@v0.1.4/src/provider/scheme/block.ts": "6a8fa821b885e8c6ca4b4e07f062cf445d36e462d9738ff1096db7ff267319db", + "https://deno.land/x/tuner@v0.1.4/src/provider/scheme/githubRes.ts": "714c4a01007d9bb04e86bc003c2e9ee9e4fbfabc0ccb8092eda2b8c2679b9ce3", + "https://deno.land/x/tuner@v0.1.4/src/provider/service.ts": "a2c920c9ccb56a4a37a7d56d0738c3c69ec2f2b7c8a78cf954b913b676c380b4", + "https://deno.land/x/tuner@v0.1.4/src/scheme.ts": "1344e2b4768e56a46d2167967447f94516d0e9458001be223d7ee03d604d5c40", + "https://deno.land/x/tuner@v0.1.4/src/tuner.ts": "dedbeba72c82a5aa0f85eaf004757ddd9d1ff48f18c7e4ec97752ec7ef0e4a6e", + "https://deno.land/x/tuner@v0.1.4/src/tunerFun.ts": "00f00a31fe9c4f5d2dca5bb395b0511ed61f503c1db7fb976d9815b4a8911be2", + "https://deno.land/x/tuner@v0.1.4/src/type.ts": "a7a201e40a6057f8b91a901cf76f440b4d19f02884bbb4f5fdb4306170a9cde5", "https://deno.land/x/url_join@1.0.0/mod.ts": "d3c7007e3ab15594e54d5b90dce623b3e274a726e0cdf57858e7ecab77a0166c", "https://deno.land/x/zod@v3.21.4/ZodError.ts": "4de18ff525e75a0315f2c12066b77b5c2ae18c7c15ef7df7e165d63536fdf2ea", "https://deno.land/x/zod@v3.21.4/errors.ts": "5285922d2be9700cc0c70c95e4858952b07ae193aa0224be3cbd5cd5567eabef", diff --git a/example/config/a.tuner.ts b/example/config/a.tuner.ts index ae59df5..bd7cb9f 100644 --- a/example/config/a.tuner.ts +++ b/example/config/a.tuner.ts @@ -5,14 +5,17 @@ export default Tuner.tune({ // 'http://localhost:8000/example/config/b.tuner.ts', // ), env: { - someField: Tuner.Env.getString.orDefault("effe"), + someField: Tuner.Env.getString.orDefault('effe'), }, - child: Tuner.Load.remote.providers.notion( - Tuner.getEnv('NOTION_KEY'), - 'https://www.notion.so/artpani/Tuner-15d96fa233e64e8da65c30bfa2ade5e2?pvs=4#8b52d24ee4e1494392ff3f1b58a73753', - ), + // child: Tuner.Load.remote.providers.notion( + // Tuner.getEnv('NOTION_KEY'), + // 'https://www.notion.so/artpani/d1ecc246b8304e08a780b9a312548064?pvs=4#ef81f9e0a6b9482db00b2045bc1a76c4', + // ), config: { - b: 200, - e: 201, + b: 209, + e: 20, + f: 780, + CONFIG_A: false, }, + watch: 5000, }); diff --git a/example/config/b.tuner.ts b/example/config/b.tuner.ts index 81fee77..b4e86b7 100644 --- a/example/config/b.tuner.ts +++ b/example/config/b.tuner.ts @@ -1,5 +1,5 @@ import Tuner from '../../mod.ts'; export default Tuner.tune({ - config: { a: 100, d: 101 }, + config: { a: 100, d: 101, CONFIG_B: true }, }); diff --git a/example/config/base.tuner.ts b/example/config/base.tuner.ts index 929bb09..5fa20ce 100644 --- a/example/config/base.tuner.ts +++ b/example/config/base.tuner.ts @@ -1,5 +1,5 @@ import Tuner from '../../mod.ts'; export default Tuner.tune({ - config: { a: 400, b: 401, c: 402 }, + config: { a: 50, b: 40, c: 402, base: false }, }); diff --git a/example/config/configSchema.ts b/example/config/configSchema.ts index fb4c6fd..638fefe 100644 --- a/example/config/configSchema.ts +++ b/example/config/configSchema.ts @@ -3,24 +3,20 @@ import { z } from 'https://deno.land/x/zod/mod.ts'; export const configSchema = z.object({ config: z.object({ a: z.number(), + d: z.number(), + CONFIG_B: z.boolean(), + test: z.number(), + develop: z.boolean(), b: z.number(), - c: z.number(), e: z.number(), - d: z.number(), + f: z.number(), + CONFIG_A: z.boolean(), + CONFIG_NOTION: z.boolean(), }), + watch: z.number(), env: z.object({ - someField: z.number(), + someField: z.string(), }), }); export type Config = z.infer; - -//├─ config -//│ ├─ a -//│ ├─ b -//│ ├─ c -//│ ├─ e -//│ └─ d -//└─ env -// └─ someField -// diff --git a/example/config/develop.tuner.ts b/example/config/develop.tuner.ts index e03a439..1026756 100644 --- a/example/config/develop.tuner.ts +++ b/example/config/develop.tuner.ts @@ -2,9 +2,10 @@ import Tuner from '../../mod.ts'; export default Tuner.tune({ child: Tuner.Load.local.cwd('example/config/a.tuner.ts'), - parent: Tuner.Load.local.configDir('base.tuner.ts'), + parent: Tuner.Load.local.cwd('example/config/b.tuner.ts'), config: { - a: 300, - b: 301, + test: 1000, + develop: true, }, + watch: 2000, }); diff --git a/example/config/main.ts b/example/config/main.ts index 183fccd..7843e15 100644 --- a/example/config/main.ts +++ b/example/config/main.ts @@ -1,9 +1,25 @@ import Tuner from '../../mod.ts'; -import { Config } from './configSchema.ts'; -const cfg = (await Tuner.use.loadConfig()) as Config; -// Tuner.use.generateSchema( +import { Ward } from '../../src/ward/ward.ts'; + +let cfg = await Tuner.use.loadConfig(); +// await Tuner.use.generateSchema( // cfg, // 'config', // 'example/config/configSchema.ts', // ); -console.log(cfg); +await Tuner.onChanged(async (_data) => { + // cfg = (await Tuner.use.loadConfig()) as Config; + cfg = await Tuner.use.loadConfig(); + console.log(cfg); +}); + +setInterval(() => { + // console.log(Ward.wards.map((ward) => ward.period).join(' ')); + console.log(Deno.memoryUsage().heapUsed); + // gc(); +}, 600); + +// const a = async (x: number) => x + 2; +// const b = async (x: number) => x + 2; + +// console.log(a.toString() === b.toString()); diff --git a/example/main.ts b/example/main.ts index a39727d..ff44ada 100644 --- a/example/main.ts +++ b/example/main.ts @@ -1,14 +1,6 @@ -// import Tuner from '../mod.ts'; -// import { Config } from './config/schema.ts'; -// const config = (await Tuner.use.loadConfig()) as Config; -// console.log(config); - -// config/develop.tuner.ts import Tuner from '../mod.ts'; -export default Tuner.tune({ - child: Tuner.Load.local.configDir('a.tuner.ts'), - config: { - a: 300, - b: 301, - }, -}); +import { Config } from './config/configSchema.ts'; + +const config = (await Tuner.use.loadConfig()) as Config; + +setInterval(() => console.log(Deno.memoryUsage()), 500); diff --git a/mod.ts b/mod.ts index 2475e79..65587e6 100644 --- a/mod.ts +++ b/mod.ts @@ -1,9 +1,10 @@ import tune from './src/tunerFun.ts'; import Env from './src/envFuns.ts'; -import { getEnv } from './src/tuner.ts'; +import { getEnv, onChangeTrigger } from './src/tuner.ts'; import Load from './src/loaders.ts'; import { loadConfig } from './src/tuner.ts'; import { generateSchema } from './src/scheme.ts'; +import { IFilledTunerConfig } from './src/type.ts'; export default { tune, @@ -14,4 +15,5 @@ export default { loadConfig, generateSchema, }, + onChanged: onChangeTrigger, }; diff --git a/src/event.ts b/src/event.ts index 35a86f0..09be9ea 100644 --- a/src/event.ts +++ b/src/event.ts @@ -13,7 +13,6 @@ const myEventTarget = new MyEventTarget(); myEventTarget.addEventListener('myEvent', (event: Event) => { const customEvent = event as CustomEvent; - console.log('Событие myEvent с данными:', customEvent.detail); }); myEventTarget.triggerCustomEvent('myEvent', { @@ -36,7 +35,6 @@ myEventTarget.triggerCustomEvent('myEvent', { // myEventTarget.addEventListener('myEvent', (event: Event) => { // const customEvent = event as CustomEvent; -// console.log('Событие myEvent с данными:', customEvent.detail); // }); // myEventTarget.triggerCustomEvent('myEvent', { diff --git a/src/loaders.ts b/src/loaders.ts index 4f056b2..9de7c54 100644 --- a/src/loaders.ts +++ b/src/loaders.ts @@ -13,8 +13,12 @@ import { */ const fromAbsolutePath = (path: string) => { - return async () => { - return (await import(path)).default as ITunerConfig; + return { + fun: async () => { + return (await import(path + `?version=${Math.random()}`)) + .default as ITunerConfig; + }, + args: path, }; }; @@ -25,18 +29,21 @@ const fromAbsolutePath = (path: string) => { * @throws Error если директория "config" не найдена. */ const fromConfigDir = (path: string) => { - return async () => { - const configDir = await findDirectoryInCWD('config'); + return { + fun: async () => { + const configDir = await findDirectoryInCWD('config'); - if (configDir === null) { - throw new Error('config directory not found'); - } + if (configDir === null) { + throw new Error('config directory not found'); + } - const module = await import( - `file:///${resolve(configDir, path)}` - ); - return module - .default as ITunerConfig; + const module = await import( + `file:///${resolve(configDir, path)}?version=${Math.random()}` + ); + return module + .default as ITunerConfig; + }, + args: path, }; }; @@ -45,9 +52,15 @@ const fromConfigDir = (path: string) => { * @param path Относительный путь к файлу с конфигурацией в текущей рабочей директории. * @returns {Promise<() => Promise>} Возвращает функцию, которая возвращает объект с конфигурацией. */ -const fromCWD = (path: string) => async () => { - return (await import(resolve('./', path))) - .default as ITunerConfig; +const fromCWD = (path: string) => { + return { + fun: async () => + (await import( + resolve('./', path + `?version=${Math.random()}`) + )) + .default as ITunerConfig, + args: path, + }; }; /** @@ -67,8 +80,13 @@ export async function importFromString(code: string) { * @param cb Функция обратного вызова, которая возвращает промис с текстом кода конфигурации в формате TypeScript. * @returns {Promise} Возвращает промис с объектом конфигурации. */ -const remoteAsString = (cb: () => Promise) => async () => { - return await importFromString(await cb()) as ITunerConfig; +const remoteAsString = (cb: () => Promise) => { + return { + fun: async () => { + return await importFromString(await cb()) as ITunerConfig; + }, + args: cb.arguments, + }; }; /** @@ -76,29 +94,43 @@ const remoteAsString = (cb: () => Promise) => async () => { * @param cb Функция обратного вызова, которая возвращает промис с объектом конфигурации в формате { default: ITunerConfig }. * @returns {Promise} Возвращает промис с объектом конфигурации. */ -const remoteAsModule = - (cb: () => Promise<{ default: ITunerConfig }>) => async () => { - return (await cb()).default as ITunerConfig; +const remoteAsModule = ( + cb: () => Promise<{ default: ITunerConfig }>, +) => { + return { + fun: async () => { + return (await cb()).default as ITunerConfig; + }, + args: cb.arguments, }; +}; /** * Загружает конфигурацию из файла с указанным источником. * @param source Путь к файлу с конфигурацией. * @returns {Promise} Возвращает промис с объектом конфигурации. */ -const remoteByImport = (source: string) => async () => { - const module = await import(source); - return module.default as ITunerConfig; +const remoteByImport = (source: string) => { + return { + fun: async () => { + const module = await import(source); + return module.default as ITunerConfig; + }, + args: source, + }; }; - /** * Функция-фабрика для получения конфигурации из Notion с использованием предоставленного ключа и идентификатора блока. * @param key Ключ аутентификации для доступа к API Notion. * @param blockUrl URL блока Notion, содержащего конфигурацию. * @returns {() => Promise} Возвращает функцию, которая возвращает объект с конфигурацией из Notion. */ -const notionLoad = (key: string, blockUrl: string) => () => - getNotionConfig(key, blockUrl); +const notionLoad = (key: string, blockUrl: string) => { + return { + fun: () => getNotionConfig(key, blockUrl), + args: [key, blockUrl], + }; +}; /** * Функция-фабрика для получения конфигурации из репозитория GitHub с использованием предоставленного API-ключа, владельца репозитория, названия репозитория и пути к файлу. @@ -108,9 +140,17 @@ const notionLoad = (key: string, blockUrl: string) => () => * @param path Путь к файлу в репозитории GitHub. * @returns {() => Promise} Возвращает функцию, которая возвращает объект с конфигурацией из репозитория GitHub. */ -const GithubLoad = - (key: string, owner: string, repo: string, path: string) => () => - getGitHubConfig(key, owner, repo, path); +const GithubLoad = ( + key: string, + owner: string, + repo: string, + path: string, +) => { + return { + fun: () => getGitHubConfig(key, owner, repo, path), + args: [key, owner, repo, path], + }; +}; /** * Загрузчик конфигурации, предоставляющий функции для получения конфигурации из различных источников. diff --git a/src/provider/service.ts b/src/provider/service.ts index 46cad88..bc98fd0 100644 --- a/src/provider/service.ts +++ b/src/provider/service.ts @@ -5,7 +5,9 @@ import axiod from 'https://deno.land/x/axiod/mod.ts'; import { GithubRes } from './scheme/githubRes.ts'; import { ITunerConfig } from '../type.ts'; import { importFromString } from '../loaders.ts'; - +import Notion from 'https://deno.land/x/integrations@v0.0.6/notion/mod.ts'; +import { CodeBlock } from 'https://deno.land/x/integrations@v0.0.6/notion/src/blockInterfaces.ts'; +import { urlToId } from 'https://deno.land/x/integrations@v0.0.6/notion/src/helpers.ts'; /** Получает конфигурацию из репозитория GitHub с использованием предоставленного API-ключа, владельца репозитория, названия репозитория и пути к файлу. @param apiKey - API-ключ для аутентификации при доступе к репозиторию GitHub. @@ -30,7 +32,6 @@ export async function getGitHubConfig( }, }, )) as unknown as GithubRes; - // console.log(atob(response.data.content)); const convertedText = await importFromString( atob(response.data.content), ) as { default: ITunerConfig }; @@ -54,16 +55,13 @@ export async function getGitHubConfig( */ export async function getNotionConfig(key: string, blockUrl: string) { try { - const notion = new Client({ - auth: key, - }); - const blockId = getBlockIdByURL(blockUrl); - console.log(blockId); - if (blockId === null) throw new Error('Invalid block'); - const response = await notion.blocks.retrieve({ - block_id: blockId, - }) as Block; - const strConfig = response.code.rich_text[0].plain_text; + const notion = new Notion({ key }); + const blockOfCode = await notion.getter.getBlockById( + urlToId.block(blockUrl), + ); + const strConfig = await notion.extractor.extractTextFromBlock( + blockOfCode, + ); const convertedText = await importFromString( strConfig, ) as { default: ITunerConfig }; diff --git a/src/tuner.ts b/src/tuner.ts index 3b52ee3..6b0fa82 100644 --- a/src/tuner.ts +++ b/src/tuner.ts @@ -2,8 +2,20 @@ import { IFilledTunerConfig, ITunerConfig } from './type.ts'; import { config as dotenvConfig } from 'https://deno.land/x/dotenv/mod.ts'; import Load from './loaders.ts'; import { MissingConfigNameEnv } from './errors.ts'; +import { Ward, WardEventData } from './ward/ward.ts'; +import { eventEmitter } from './ward/eventManager.ts'; -type configList = { [key: number]: ITunerConfig }; +type ConfigList = { + [key: number]: { + config: ITunerConfig; + delivery: () => + | ITunerConfig + | Promise + | IFilledTunerConfig + | Promise; + // args?: any; + }; +}; /** * Получает значение переменной окружения по указанному имени. @@ -27,10 +39,15 @@ export function getEnv(name: string): string { export async function loadConfig(): Promise { const configName = getEnv('config'); const mainConfig = - await (await Load.local.configDir(`${configName}.tuner.ts`)()); + await (await Load.local.configDir(`${configName}.tuner.ts`) + .fun()); + eventEmitter.removeAllListeners( + 'STOP_ALL_WARDS', + ); + Ward.stopAllWards(); const configSequence = await inheritList(mainConfig); const mergedConfig = mergeSequentialConfigs(configSequence); - return fillEnv(mergedConfig); + return fillEnv(await mergedConfig); } /** @@ -96,20 +113,33 @@ function mergeConfigs( */ async function inheritList( curConfig: ITunerConfig, - store: configList = {}, -): Promise { - store[0] = curConfig; + store: ConfigList = {}, +): Promise { + store[0] = { + config: curConfig, + delivery: async () => { + return await Load.local.configDir( + `${getEnv('config')}.tuner.ts`, + ).fun(); + }, + }; let i = 0; while (curConfig.child) { - const childConfig = await curConfig.child(); - store[--i] = childConfig; + const childConfig = await curConfig.child.fun(); + store[--i] = { + config: childConfig, + delivery: curConfig.child.fun, + }; curConfig = childConfig; } i = 0; - curConfig = store[0]; + curConfig = store[0].config; while (curConfig.parent) { - const parentConfig = await curConfig.parent(); - store[++i] = parentConfig; + const parentConfig = await curConfig.parent.fun(); + store[++i] = { + config: parentConfig, + delivery: curConfig.parent.fun, + }; curConfig = parentConfig; } return store; @@ -120,12 +150,28 @@ async function inheritList( * @param configs Массив с последовательностью конфигураций. * @returns Объединенная конфигурация. */ -function mergeSequentialConfigs(configs: configList): ITunerConfig { +async function mergeSequentialConfigs( + configs: ConfigList, +): Promise { let mergedConfig: ITunerConfig | null = null; const sortedKeys = Object.keys(configs).sort((a, b) => +b - +a); for (const key of sortedKeys) { - const currentConfig = configs[Number(key)]; - + // console.log(configs[Number(key)].config); + const currentConfig = configs[Number(key)].config; + if (currentConfig.watch) { + // console.log('Начинаю наблюдение за конфигом:'); + // console.log(currentConfig); + const ward = new Ward() + .target.data.remote( + configs[Number(key)].delivery as () => Promise< + IFilledTunerConfig + >, + ) + .time(currentConfig.watch * 2) + .ifChangedThen.emitEvent('CONFIG_CHANGE') + .build(); + await ward.start(); + } if (mergedConfig === null) { mergedConfig = currentConfig; } else { @@ -135,3 +181,16 @@ function mergeSequentialConfigs(configs: configList): ITunerConfig { return mergedConfig as ITunerConfig; } + +export async function onChangeTrigger( + cb: (data: WardEventData) => void | Promise, +) { + eventEmitter.addListener('CONFIG_CHANGE', cb); +} + +// function setWatching( +// manager: EventManager, +// config: ITunerConfig, +// delivery: () => ITunerConfig | Promise, +// ) { +// } diff --git a/src/type.ts b/src/type.ts index c4e9971..ebfbd0d 100644 --- a/src/type.ts +++ b/src/type.ts @@ -14,22 +14,31 @@ type LoadAsyncFun = (path: string) => | Promise, ) => Promise); +type ParentOrChild = { + fun: () => Promise; + args: any; +}; + +// () => Promise<{ fun: ITunerConfig; args: string; }> + export interface ITunerConfig { - parent?: () => Promise; - child?: () => Promise; + parent?: ParentOrChild; + child?: ParentOrChild; env?: { [key: string]: EnvFun | EnvAsyncFun | Primitive; }; config?: {}; + watch?: number; } export interface IFilledTunerConfig { - parent?: LoadAsyncFun; - child?: LoadAsyncFun; + parent?: ParentOrChild; + child?: ParentOrChild; env?: { [key: string]: Primitive; }; config?: {}; + watch?: number; } diff --git a/src/ward/errors.ts b/src/ward/errors.ts new file mode 100644 index 0000000..eb803d5 --- /dev/null +++ b/src/ward/errors.ts @@ -0,0 +1,17 @@ +export class InvalidPeriodFormat extends Error { + constructor() { + super('Invalid period format'); + } +} + +export class NonComparableItems extends Error { + constructor(a: unknown, b: unknown) { + super(`Cannot compare ${typeof a} and ${typeof b}`); + } +} + +export class NonCopyItems extends Error { + constructor(a: unknown, b: unknown) { + super(`Cannot copy ${typeof a} to ${typeof b}`); + } +} diff --git a/src/ward/eventManager.ts b/src/ward/eventManager.ts new file mode 100644 index 0000000..13a485b --- /dev/null +++ b/src/ward/eventManager.ts @@ -0,0 +1,3 @@ +import { EventEmitter } from 'https://deno.land/std@0.90.0/node/events.ts'; + +export const eventEmitter = new EventEmitter(); diff --git a/src/ward/ward.ts b/src/ward/ward.ts new file mode 100644 index 0000000..8045166 --- /dev/null +++ b/src/ward/ward.ts @@ -0,0 +1,267 @@ +import { InvalidPeriodFormat, NonComparableItems } from './errors.ts'; +import { eventEmitter } from './eventManager.ts'; +type TargetType = + | 'localFile' + | 'remoteFile' + | 'variable' + | 'remoteData'; + +// const reg = new FinalizationRegistry((id: number) => { +// console.log(`Вард ${id} был уничтожен!`); +// }); + +type remoteCb = () => Promise; +export interface WardEventData { + old: object | string | T; + new: object | string | T; + timeOfLastChange: number; +} + +export class Ward { + static wards: Ward[] = []; + + static stopAllWards: () => void = () => { + Ward.wards.forEach((ward) => ward.stop()); + Ward.wards = []; + }; + private title = ''; + private period = 500; + private timeoutId = 0; + private localPath = ''; + private lastChangeDetectedTimestamp = 0; + private typeData: TargetType = 'variable'; + private conditionToStop: ( + data: WardEventData, + ) => boolean = () => false; + + private lastMemo: T | string | null = null; + private dataCb: remoteCb | null = null; + + private handler: ( + oldValue: T | string, + newValue: T | string, + ) => void = () => {}; + + public event = ''; + + // private watchedObjectRef: WeakRef | null = null; + + name(title: string) { + this.title = title; + return this; + } + + public target = { + file: { + local: (path: string) => { + this.localPath = path; + this.typeData = 'localFile'; + return this; + }, + }, + data: { + remote: (cb: () => Promise) => { + this.dataCb = cb; + this.typeData = 'remoteData'; + return this; + }, + }, + }; + + time(period: number) { + this.period = period; + return this; + } + + public build() { + Ward.wards.push(this); + + return this; + } + + public ifChangedThen = { + runCallback: ( + callback: (oldValue: T | string, newValue: T | string) => void, + ) => { + this.handler = callback; + return this; + }, + emitEvent: (eventName: string) => { + this.event = eventName; + this.handler = (oldValue: T | string, newValue: T | string) => { + eventEmitter.emit(eventName, { + old: oldValue, + new: newValue, + timeOfLastChange: this.lastChangeDetectedTimestamp, + }); + }; + return this; + }, + }; + + public stopIf = { + condition: ( + predicate: ( + data: WardEventData, + ) => boolean, + ) => { + this.conditionToStop = predicate; + return this; + }, + eventTriggered: (eventName: string) => { + eventEmitter.on( + eventName, + (_data?: WardEventData) => { + this.stop(); + }, + ); + return this; + }, + }; + + async grab() { + switch (this.typeData) { + case 'remoteData': + return await this.dataCb!(); + case 'localFile': + return await Deno.readTextFile(this.localPath); + // case 'variable': + // return this.watchedObjectRef?.deref() as T; + default: + return {} as T; + } + } + + deepCopy(obj: any): any { + if (typeof obj !== 'object' || obj === null) { + return obj; + } + + if (obj instanceof Date) { + return new Date(obj); + } + + if (obj instanceof Array) { + const copy = []; + for (const item of obj) { + copy.push(this.deepCopy(item)); + } + + return copy; + } + + if (obj instanceof Object) { + const copy: Record = {}; + for (const key in obj) { + if (obj.hasOwnProperty(key)) { + copy[key] = this.deepCopy(obj[key]); + } + } + return copy; + } + + throw new Error('Unable to copy object.'); + } + + deepEqual(obj1: any, obj2: any): boolean { + if (obj1 === obj2) { + return true; + } + + if ( + typeof obj1 !== 'object' || typeof obj2 !== 'object' || + obj1 === null || obj2 === null + ) { + return false; + } + + if (Object.keys(obj1).length !== Object.keys(obj2).length) { + return false; + } + + const areFunctions = (x: any, y: any) => + typeof x === 'function' && typeof y === 'function' && + x.toString() === y.toString(); + + for (const key in obj1) { + if (!obj2.hasOwnProperty(key)) { + return false; + } + + const val1 = obj1[key]; + const val2 = obj2[key]; + + if (!areFunctions(val1, val2) && !this.deepEqual(val1, val2)) { + return false; + } + } + + return true; + } + + equal(a: object | string, b: object | string): boolean { + if (typeof a == 'object' && typeof b == 'object') { + return this.deepEqual(a, b); + } + if (typeof a == 'string' && typeof b == 'string') { + return a == b; + } + throw new NonComparableItems(a, b); + } + + copy(target: object | string): object | string { + return typeof target == 'object' ? this.deepCopy(target) : target; + } + + async start() { + if (this.lastMemo === null) { + this.lastMemo = await this.grab(); + } + const milliseconds = this.period; + this.timeoutId = setInterval(async () => { + // console.log(eventEmitter.listeners('CONFIG_CHANGE')); + // console.log(Ward.wards.length); + const newData = await this.grab(); + + if ( + this.conditionToStop({ + old: this.lastMemo!, + new: this.deepCopy(newData), + timeOfLastChange: this.lastChangeDetectedTimestamp, + }) + ) { + this.stop(); + return; + } + // console.log(newData, this.lastMemo); + // console.log(this.equal(newData, this.lastMemo!)); + if (!this.equal(newData, this.lastMemo!)) { + this.handler(this.lastMemo!, newData); + this.lastMemo = this.copy(newData) as typeof newData; + this.lastChangeDetectedTimestamp = Date.now(); + } + }, milliseconds); + // console.log(`${this.timeoutId} created`); + // reg.register(this, this.timeoutId); + } + + stop() { + console.log(`Ward ${this.title} stopped`); + clearInterval(this.timeoutId); + } + + // private parseMilliseconds() { + // const stringTimes = this.period.split('/'); + // if (stringTimes.length !== 3) { + // throw new InvalidPeriodFormat(); + // } + // const [minutes, seconds, milliseconds] = stringTimes + // .map((part) => part === '*' ? 0 : parseInt(part, 10)); + // if (milliseconds == 0 && minutes == 0 && seconds == 0) { + // return 5000; + // } + // const totalMilliseconds = (minutes * 60 * 1000) + + // (seconds * 1000) + milliseconds; + // return totalMilliseconds; + // } +}