@autofleet/sadot 1.7.6 → 1.8.1-beta.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.
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
const e=require(`../utils/logger/index.cjs`),
|
|
1
|
+
const e=require(`../_virtual/rolldown_runtime.cjs`),t=require(`../utils/logger/index.cjs`),n=require(`./001-create-core-tables.cjs`),r=require(`./002-create-custom-field-entries.cjs`),i=require(`./003-create-custom-field-model-type-map.cjs`);let a=require(`node:timers/promises`);const{Umzug:o,SequelizeStorage:s}=require(`umzug`),c=e=>{let{useCustomFieldsEntries:t=!1,useModelTypeMapping:a=!1}=e;return[{name:`001-create-core-tables`,up:n.up,down:n.down},...t?[{name:`002-create-custom-field-entries`,up:r.up,down:r.down}]:[],...a?[{name:`003-create-custom-field-model-type-map`,up:i.up,down:i.down}]:[]]},l=839274628,u=async(e,n={})=>{let r=e.getQueryInterface(),i=new s({sequelize:e,tableName:`sadot_migrations`,modelName:`SadotMigrations`}),u=new o({migrations:c(n),context:r,storage:i,logger:{info:e=>t.default.info(`[sadot] ${JSON.stringify(e)}`),warn:e=>t.default.warn(`[sadot] ${JSON.stringify(e)}`),error:e=>t.default.error(`[sadot] ${JSON.stringify(e)}`),debug:e=>t.default.debug(`[sadot] ${JSON.stringify(e)}`)}}),d=await u.pending();if(d.length===0){t.default.info(`[sadot] no pending migrations, skipping`);return}if(t.default.info(`[sadot] pending migrations detected`,{pending:d.map(e=>e.name)}),(e.options?.pool?.max??5)<2){t.default.info(`[sadot] connection pool too small for advisory lock, running migrations directly`),await u.up();return}t.default.info(`[sadot] acquiring advisory lock`);let f=await e.connectionManager.getConnection({type:`write`});try{let e=!1;for(;!e;)e=(await f.query(`SELECT pg_try_advisory_lock(${l}) AS acquired`)).rows[0].acquired,e||await(0,a.setTimeout)(1e3);t.default.info(`[sadot] advisory lock acquired`),(await u.pending()).length>0?await u.up():t.default.info(`[sadot] migrations already applied by another instance, skipping`)}finally{try{await f.query(`SELECT pg_advisory_unlock(${l})`),t.default.info(`[sadot] advisory lock released`)}finally{e.connectionManager.releaseConnection(f)}}};exports.runSadotMigrations=u;
|
|
2
2
|
//# sourceMappingURL=index.cjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":["logger"],"sources":["../../src/migrations/index.ts"],"sourcesContent":["import type { InputMigrations } from 'umzug';\nimport type { QueryInterface } from 'sequelize';\nimport type { Sequelize } from 'sequelize-typescript';\nimport logger from '../utils/logger';\nimport * as migration001 from './001-create-core-tables';\nimport * as migration002 from './002-create-custom-field-entries';\nimport * as migration003 from './003-create-custom-field-model-type-map';\n\n// umzug is CJS. Static ESM named imports break in vitest after vi.resetModules()\n// because Vite's SSR transform re-evaluates the module outside the inline bundle.\n// require() goes through Node's native CJS loader and is stable across resets.\n\n// eslint-disable-next-line import/no-commonjs\nconst { Umzug, SequelizeStorage } = require('umzug') as typeof import('umzug');\n\ninterface MigratorOptions {\n useCustomFieldsEntries?: boolean;\n useModelTypeMapping?: boolean;\n}\n\nconst buildMigrationList = (options: MigratorOptions): InputMigrations<QueryInterface> => {\n const { useCustomFieldsEntries = false, useModelTypeMapping = false } = options;\n return [\n { name: '001-create-core-tables', up: migration001.up, down: migration001.down },\n ...(useCustomFieldsEntries ? [{ name: '002-create-custom-field-entries', up: migration002.up, down: migration002.down }] : []),\n ...(useModelTypeMapping ? [{ name: '003-create-custom-field-model-type-map', up: migration003.up, down: migration003.down }] : []),\n ];\n};\n\n// Stable advisory-lock ID for sadot migrations. Chosen arbitrarily; must not\n// collide with other advisory locks in the same database.\nconst SADOT_MIGRATION_LOCK_ID = 839274628;\n\n/**\n * Runs sadot migrations via umzug. All migrations use IF NOT EXISTS / IF EXISTS,\n * so they are fully idempotent — safe to run against both fresh and existing\n * databases (including DBs previously managed by the legacy sync() path).\n *\n * A PostgreSQL advisory lock is acquired before running migrations so that when\n * multiple pods start concurrently only one of them executes the migrations.\n * The remaining pods block until the lock is released, then proceed — Umzug will\n * see the migrations have already been applied and skip them.\n */\nconst runSadotMigrations = async (sequelize: Sequelize, options: MigratorOptions = {}): Promise<void> => {\n // Need to add a new migration? full migration guide here; https://www.notion.so/autofleet/How-to-Add-a-New-Migration-to-Sadot-36db603f40b98023b239d3c61aa22b00\n const queryInterface = sequelize.getQueryInterface() as unknown as QueryInterface;\n const storage = new SequelizeStorage({ sequelize: sequelize as never, tableName: 'sadot_migrations', modelName: 'SadotMigrations' });\n\n const umzugLogger = {\n info: (msg: Record<string, unknown>) => logger.info(`[sadot] ${JSON.stringify(msg)}`),\n warn: (msg: Record<string, unknown>) => logger.warn(`[sadot] ${JSON.stringify(msg)}`),\n error: (msg: Record<string, unknown>) => logger.error(`[sadot] ${JSON.stringify(msg)}`),\n debug: (msg: Record<string, unknown>) => logger.debug(`[sadot] ${JSON.stringify(msg)}`),\n };\n\n const migrator = new Umzug<QueryInterface>({\n migrations: buildMigrationList(options),\n context: queryInterface,\n storage,\n logger: umzugLogger,\n });\n\n const pending = await migrator.pending();\n if (pending.length === 0) {\n logger.info('[sadot] no pending migrations, skipping');\n return;\n }\n\n logger.info('[sadot] pending migrations detected', { pending: pending.map(m => m.name) });\n\n // The advisory lock holds a connection for its entire duration while\n // migrator.up() needs additional pool connections for queries. With\n // pool.max < 2 (common in test environments) this deadlocks.\n // Migrations are idempotent (IF NOT EXISTS / IF EXISTS) so running\n // without the lock is safe — just potentially duplicated work.\n const poolMax = (sequelize.options as any)?.pool?.max ?? 5;\n if (poolMax < 2) {\n logger.info('[sadot] connection pool too small for advisory lock, running migrations directly');\n await migrator.up();\n return;\n }\n\n // Use a dedicated raw connection (not a transaction) to hold the advisory lock.\n // A transaction connection sitting idle while migrator.up() runs on other pool\n // connections would be killed by idle_in_transaction_session_timeout, releasing\n // the lock prematurely and allowing a second replica to start migrations concurrently.\n logger.info('[sadot] acquiring advisory lock');\n const lockConnection = await (sequelize.connectionManager as any).getConnection({ type: 'write' });\n try {\n //
|
|
1
|
+
{"version":3,"file":"index.cjs","names":["logger"],"sources":["../../src/migrations/index.ts"],"sourcesContent":["import type { InputMigrations } from 'umzug';\nimport type { QueryInterface } from 'sequelize';\nimport type { Sequelize } from 'sequelize-typescript';\nimport { setTimeout } from 'node:timers/promises';\nimport logger from '../utils/logger';\nimport * as migration001 from './001-create-core-tables';\nimport * as migration002 from './002-create-custom-field-entries';\nimport * as migration003 from './003-create-custom-field-model-type-map';\n\n// umzug is CJS. Static ESM named imports break in vitest after vi.resetModules()\n// because Vite's SSR transform re-evaluates the module outside the inline bundle.\n// require() goes through Node's native CJS loader and is stable across resets.\n\n// eslint-disable-next-line import/no-commonjs\nconst { Umzug, SequelizeStorage } = require('umzug') as typeof import('umzug');\n\ninterface MigratorOptions {\n useCustomFieldsEntries?: boolean;\n useModelTypeMapping?: boolean;\n}\n\nconst buildMigrationList = (options: MigratorOptions): InputMigrations<QueryInterface> => {\n const { useCustomFieldsEntries = false, useModelTypeMapping = false } = options;\n return [\n { name: '001-create-core-tables', up: migration001.up, down: migration001.down },\n ...(useCustomFieldsEntries ? [{ name: '002-create-custom-field-entries', up: migration002.up, down: migration002.down }] : []),\n ...(useModelTypeMapping ? [{ name: '003-create-custom-field-model-type-map', up: migration003.up, down: migration003.down }] : []),\n ];\n};\n\n// Stable advisory-lock ID for sadot migrations. Chosen arbitrarily; must not\n// collide with other advisory locks in the same database.\nconst SADOT_MIGRATION_LOCK_ID = 839274628;\n\n/**\n * Runs sadot migrations via umzug. All migrations use IF NOT EXISTS / IF EXISTS,\n * so they are fully idempotent — safe to run against both fresh and existing\n * databases (including DBs previously managed by the legacy sync() path).\n *\n * A PostgreSQL advisory lock is acquired before running migrations so that when\n * multiple pods start concurrently only one of them executes the migrations.\n * The remaining pods block until the lock is released, then proceed — Umzug will\n * see the migrations have already been applied and skip them.\n */\nconst runSadotMigrations = async (sequelize: Sequelize, options: MigratorOptions = {}): Promise<void> => {\n // Need to add a new migration? full migration guide here; https://www.notion.so/autofleet/How-to-Add-a-New-Migration-to-Sadot-36db603f40b98023b239d3c61aa22b00\n const queryInterface = sequelize.getQueryInterface() as unknown as QueryInterface;\n const storage = new SequelizeStorage({ sequelize: sequelize as never, tableName: 'sadot_migrations', modelName: 'SadotMigrations' });\n\n const umzugLogger = {\n info: (msg: Record<string, unknown>) => logger.info(`[sadot] ${JSON.stringify(msg)}`),\n warn: (msg: Record<string, unknown>) => logger.warn(`[sadot] ${JSON.stringify(msg)}`),\n error: (msg: Record<string, unknown>) => logger.error(`[sadot] ${JSON.stringify(msg)}`),\n debug: (msg: Record<string, unknown>) => logger.debug(`[sadot] ${JSON.stringify(msg)}`),\n };\n\n const migrator = new Umzug<QueryInterface>({\n migrations: buildMigrationList(options),\n context: queryInterface,\n storage,\n logger: umzugLogger,\n });\n\n const pending = await migrator.pending();\n if (pending.length === 0) {\n logger.info('[sadot] no pending migrations, skipping');\n return;\n }\n\n logger.info('[sadot] pending migrations detected', { pending: pending.map(m => m.name) });\n\n // The advisory lock holds a connection for its entire duration while\n // migrator.up() needs additional pool connections for queries. With\n // pool.max < 2 (common in test environments) this deadlocks.\n // Migrations are idempotent (IF NOT EXISTS / IF EXISTS) so running\n // without the lock is safe — just potentially duplicated work.\n const poolMax = (sequelize.options as any)?.pool?.max ?? 5;\n if (poolMax < 2) {\n logger.info('[sadot] connection pool too small for advisory lock, running migrations directly');\n await migrator.up();\n return;\n }\n\n // Use a dedicated raw connection (not a transaction) to hold the advisory lock.\n // A transaction connection sitting idle while migrator.up() runs on other pool\n // connections would be killed by idle_in_transaction_session_timeout, releasing\n // the lock prematurely and allowing a second replica to start migrations concurrently.\n logger.info('[sadot] acquiring advisory lock');\n const lockConnection = await (sequelize.connectionManager as any).getConnection({ type: 'write' });\n try {\n // Poll with pg_try_advisory_lock so the connection stays idle between attempts rather\n // than holding an active statement for the full wait duration. DDL operations that track\n // active backends between internal phases (e.g. CREATE INDEX CONCURRENTLY, VACUUM,\n // REINDEX CONCURRENTLY) wait for all active statements to finish before proceeding —\n // a long-blocking lock attempt would stall them for the entire wait.\n let lockAcquired = false;\n while (!lockAcquired) {\n const result = await lockConnection.query(`SELECT pg_try_advisory_lock(${SADOT_MIGRATION_LOCK_ID}) AS acquired`);\n lockAcquired = result.rows[0].acquired;\n if (!lockAcquired) await setTimeout(1000);\n }\n logger.info('[sadot] advisory lock acquired');\n\n // Re-check after acquiring the lock — another pod may have already\n // applied the migrations while we were waiting.\n const stillPending = await migrator.pending();\n if (stillPending.length > 0) {\n await migrator.up();\n } else {\n logger.info('[sadot] migrations already applied by another instance, skipping');\n }\n } finally {\n try {\n await lockConnection.query(`SELECT pg_advisory_unlock(${SADOT_MIGRATION_LOCK_ID})`);\n logger.info('[sadot] advisory lock released');\n } finally {\n (sequelize.connectionManager as any).releaseConnection(lockConnection);\n }\n }\n};\n\nexport { runSadotMigrations };\n"],"mappings":"wRAcA,KAAM,CAAE,QAAO,oBAAqB,QAAQ,QAAQ,CAO9C,EAAsB,GAA8D,CACxF,GAAM,CAAE,yBAAyB,GAAO,sBAAsB,IAAU,EACxE,MAAO,CACL,CAAE,KAAM,yBAA0B,GAAA,EAAA,GAAqB,KAAA,EAAA,KAAyB,CAChF,GAAI,EAAyB,CAAC,CAAE,KAAM,kCAAmC,GAAA,EAAA,GAAqB,KAAA,EAAA,KAAyB,CAAC,CAAG,EAAE,CAC7H,GAAI,EAAsB,CAAC,CAAE,KAAM,yCAA0C,GAAA,EAAA,GAAqB,KAAA,EAAA,KAAyB,CAAC,CAAG,EAAE,CAClI,EAKG,EAA0B,UAY1B,EAAqB,MAAO,EAAsB,EAA2B,EAAE,GAAoB,CAEvG,IAAM,EAAiB,EAAU,mBAAmB,CAC9C,EAAU,IAAI,EAAiB,CAAa,YAAoB,UAAW,mBAAoB,UAAW,kBAAmB,CAAC,CAS9H,EAAW,IAAI,EAAsB,CACzC,WAAY,EAAmB,EAAQ,CACvC,QAAS,EACT,UACA,OAXkB,CAClB,KAAO,GAAiCA,EAAAA,QAAO,KAAK,WAAW,KAAK,UAAU,EAAI,GAAG,CACrF,KAAO,GAAiCA,EAAAA,QAAO,KAAK,WAAW,KAAK,UAAU,EAAI,GAAG,CACrF,MAAQ,GAAiCA,EAAAA,QAAO,MAAM,WAAW,KAAK,UAAU,EAAI,GAAG,CACvF,MAAQ,GAAiCA,EAAAA,QAAO,MAAM,WAAW,KAAK,UAAU,EAAI,GAAG,CACxF,CAOA,CAAC,CAEI,EAAU,MAAM,EAAS,SAAS,CACxC,GAAI,EAAQ,SAAW,EAAG,CACxB,EAAA,QAAO,KAAK,0CAA0C,CACtD,OAWF,GARA,EAAA,QAAO,KAAK,sCAAuC,CAAE,QAAS,EAAQ,IAAI,GAAK,EAAE,KAAK,CAAE,CAAC,EAOxE,EAAU,SAAiB,MAAM,KAAO,GAC3C,EAAG,CACf,EAAA,QAAO,KAAK,mFAAmF,CAC/F,MAAM,EAAS,IAAI,CACnB,OAOF,EAAA,QAAO,KAAK,kCAAkC,CAC9C,IAAM,EAAiB,MAAO,EAAU,kBAA0B,cAAc,CAAE,KAAM,QAAS,CAAC,CAClG,GAAI,CAMF,IAAI,EAAe,GACnB,KAAO,CAAC,GAEN,GADe,MAAM,EAAe,MAAM,+BAA+B,EAAwB,eAAe,EAC1F,KAAK,GAAG,SACzB,GAAc,MAAA,EAAA,EAAA,YAAiB,IAAK,CAE3C,EAAA,QAAO,KAAK,iCAAiC,EAIxB,MAAM,EAAS,SAAS,EAC5B,OAAS,EACxB,MAAM,EAAS,IAAI,CAEnB,EAAA,QAAO,KAAK,mEAAmE,QAEzE,CACR,GAAI,CACF,MAAM,EAAe,MAAM,6BAA6B,EAAwB,GAAG,CACnF,EAAA,QAAO,KAAK,iCAAiC,QACrC,CACP,EAAU,kBAA0B,kBAAkB,EAAe"}
|
package/dist/migrations/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{__require as e}from"../_virtual/rolldown_runtime.js";import t from"../utils/logger/index.js";import{down as n,up as r}from"./001-create-core-tables.js";import{down as i,up as a}from"./002-create-custom-field-entries.js";import{down as o,up as s}from"./003-create-custom-field-model-type-map.js";const{Umzug:
|
|
1
|
+
import{__require as e}from"../_virtual/rolldown_runtime.js";import t from"../utils/logger/index.js";import{down as n,up as r}from"./001-create-core-tables.js";import{down as i,up as a}from"./002-create-custom-field-entries.js";import{down as o,up as s}from"./003-create-custom-field-model-type-map.js";import{setTimeout as c}from"node:timers/promises";const{Umzug:l,SequelizeStorage:u}=e(`umzug`),d=e=>{let{useCustomFieldsEntries:t=!1,useModelTypeMapping:c=!1}=e;return[{name:`001-create-core-tables`,up:r,down:n},...t?[{name:`002-create-custom-field-entries`,up:a,down:i}]:[],...c?[{name:`003-create-custom-field-model-type-map`,up:s,down:o}]:[]]},f=839274628,p=async(e,n={})=>{let r=e.getQueryInterface(),i=new u({sequelize:e,tableName:`sadot_migrations`,modelName:`SadotMigrations`}),a=new l({migrations:d(n),context:r,storage:i,logger:{info:e=>t.info(`[sadot] ${JSON.stringify(e)}`),warn:e=>t.warn(`[sadot] ${JSON.stringify(e)}`),error:e=>t.error(`[sadot] ${JSON.stringify(e)}`),debug:e=>t.debug(`[sadot] ${JSON.stringify(e)}`)}}),o=await a.pending();if(o.length===0){t.info(`[sadot] no pending migrations, skipping`);return}if(t.info(`[sadot] pending migrations detected`,{pending:o.map(e=>e.name)}),(e.options?.pool?.max??5)<2){t.info(`[sadot] connection pool too small for advisory lock, running migrations directly`),await a.up();return}t.info(`[sadot] acquiring advisory lock`);let s=await e.connectionManager.getConnection({type:`write`});try{let e=!1;for(;!e;)e=(await s.query(`SELECT pg_try_advisory_lock(${f}) AS acquired`)).rows[0].acquired,e||await c(1e3);t.info(`[sadot] advisory lock acquired`),(await a.pending()).length>0?await a.up():t.info(`[sadot] migrations already applied by another instance, skipping`)}finally{try{await s.query(`SELECT pg_advisory_unlock(${f})`),t.info(`[sadot] advisory lock released`)}finally{e.connectionManager.releaseConnection(s)}}};export{p as runSadotMigrations};
|
|
2
2
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["migration001.up","migration001.down","migration002.up","migration002.down","migration003.up","migration003.down","logger"],"sources":["../../src/migrations/index.ts"],"sourcesContent":["import type { InputMigrations } from 'umzug';\nimport type { QueryInterface } from 'sequelize';\nimport type { Sequelize } from 'sequelize-typescript';\nimport logger from '../utils/logger';\nimport * as migration001 from './001-create-core-tables';\nimport * as migration002 from './002-create-custom-field-entries';\nimport * as migration003 from './003-create-custom-field-model-type-map';\n\n// umzug is CJS. Static ESM named imports break in vitest after vi.resetModules()\n// because Vite's SSR transform re-evaluates the module outside the inline bundle.\n// require() goes through Node's native CJS loader and is stable across resets.\n\n// eslint-disable-next-line import/no-commonjs\nconst { Umzug, SequelizeStorage } = require('umzug') as typeof import('umzug');\n\ninterface MigratorOptions {\n useCustomFieldsEntries?: boolean;\n useModelTypeMapping?: boolean;\n}\n\nconst buildMigrationList = (options: MigratorOptions): InputMigrations<QueryInterface> => {\n const { useCustomFieldsEntries = false, useModelTypeMapping = false } = options;\n return [\n { name: '001-create-core-tables', up: migration001.up, down: migration001.down },\n ...(useCustomFieldsEntries ? [{ name: '002-create-custom-field-entries', up: migration002.up, down: migration002.down }] : []),\n ...(useModelTypeMapping ? [{ name: '003-create-custom-field-model-type-map', up: migration003.up, down: migration003.down }] : []),\n ];\n};\n\n// Stable advisory-lock ID for sadot migrations. Chosen arbitrarily; must not\n// collide with other advisory locks in the same database.\nconst SADOT_MIGRATION_LOCK_ID = 839274628;\n\n/**\n * Runs sadot migrations via umzug. All migrations use IF NOT EXISTS / IF EXISTS,\n * so they are fully idempotent — safe to run against both fresh and existing\n * databases (including DBs previously managed by the legacy sync() path).\n *\n * A PostgreSQL advisory lock is acquired before running migrations so that when\n * multiple pods start concurrently only one of them executes the migrations.\n * The remaining pods block until the lock is released, then proceed — Umzug will\n * see the migrations have already been applied and skip them.\n */\nconst runSadotMigrations = async (sequelize: Sequelize, options: MigratorOptions = {}): Promise<void> => {\n // Need to add a new migration? full migration guide here; https://www.notion.so/autofleet/How-to-Add-a-New-Migration-to-Sadot-36db603f40b98023b239d3c61aa22b00\n const queryInterface = sequelize.getQueryInterface() as unknown as QueryInterface;\n const storage = new SequelizeStorage({ sequelize: sequelize as never, tableName: 'sadot_migrations', modelName: 'SadotMigrations' });\n\n const umzugLogger = {\n info: (msg: Record<string, unknown>) => logger.info(`[sadot] ${JSON.stringify(msg)}`),\n warn: (msg: Record<string, unknown>) => logger.warn(`[sadot] ${JSON.stringify(msg)}`),\n error: (msg: Record<string, unknown>) => logger.error(`[sadot] ${JSON.stringify(msg)}`),\n debug: (msg: Record<string, unknown>) => logger.debug(`[sadot] ${JSON.stringify(msg)}`),\n };\n\n const migrator = new Umzug<QueryInterface>({\n migrations: buildMigrationList(options),\n context: queryInterface,\n storage,\n logger: umzugLogger,\n });\n\n const pending = await migrator.pending();\n if (pending.length === 0) {\n logger.info('[sadot] no pending migrations, skipping');\n return;\n }\n\n logger.info('[sadot] pending migrations detected', { pending: pending.map(m => m.name) });\n\n // The advisory lock holds a connection for its entire duration while\n // migrator.up() needs additional pool connections for queries. With\n // pool.max < 2 (common in test environments) this deadlocks.\n // Migrations are idempotent (IF NOT EXISTS / IF EXISTS) so running\n // without the lock is safe — just potentially duplicated work.\n const poolMax = (sequelize.options as any)?.pool?.max ?? 5;\n if (poolMax < 2) {\n logger.info('[sadot] connection pool too small for advisory lock, running migrations directly');\n await migrator.up();\n return;\n }\n\n // Use a dedicated raw connection (not a transaction) to hold the advisory lock.\n // A transaction connection sitting idle while migrator.up() runs on other pool\n // connections would be killed by idle_in_transaction_session_timeout, releasing\n // the lock prematurely and allowing a second replica to start migrations concurrently.\n logger.info('[sadot] acquiring advisory lock');\n const lockConnection = await (sequelize.connectionManager as any).getConnection({ type: 'write' });\n try {\n //
|
|
1
|
+
{"version":3,"file":"index.js","names":["migration001.up","migration001.down","migration002.up","migration002.down","migration003.up","migration003.down","logger"],"sources":["../../src/migrations/index.ts"],"sourcesContent":["import type { InputMigrations } from 'umzug';\nimport type { QueryInterface } from 'sequelize';\nimport type { Sequelize } from 'sequelize-typescript';\nimport { setTimeout } from 'node:timers/promises';\nimport logger from '../utils/logger';\nimport * as migration001 from './001-create-core-tables';\nimport * as migration002 from './002-create-custom-field-entries';\nimport * as migration003 from './003-create-custom-field-model-type-map';\n\n// umzug is CJS. Static ESM named imports break in vitest after vi.resetModules()\n// because Vite's SSR transform re-evaluates the module outside the inline bundle.\n// require() goes through Node's native CJS loader and is stable across resets.\n\n// eslint-disable-next-line import/no-commonjs\nconst { Umzug, SequelizeStorage } = require('umzug') as typeof import('umzug');\n\ninterface MigratorOptions {\n useCustomFieldsEntries?: boolean;\n useModelTypeMapping?: boolean;\n}\n\nconst buildMigrationList = (options: MigratorOptions): InputMigrations<QueryInterface> => {\n const { useCustomFieldsEntries = false, useModelTypeMapping = false } = options;\n return [\n { name: '001-create-core-tables', up: migration001.up, down: migration001.down },\n ...(useCustomFieldsEntries ? [{ name: '002-create-custom-field-entries', up: migration002.up, down: migration002.down }] : []),\n ...(useModelTypeMapping ? [{ name: '003-create-custom-field-model-type-map', up: migration003.up, down: migration003.down }] : []),\n ];\n};\n\n// Stable advisory-lock ID for sadot migrations. Chosen arbitrarily; must not\n// collide with other advisory locks in the same database.\nconst SADOT_MIGRATION_LOCK_ID = 839274628;\n\n/**\n * Runs sadot migrations via umzug. All migrations use IF NOT EXISTS / IF EXISTS,\n * so they are fully idempotent — safe to run against both fresh and existing\n * databases (including DBs previously managed by the legacy sync() path).\n *\n * A PostgreSQL advisory lock is acquired before running migrations so that when\n * multiple pods start concurrently only one of them executes the migrations.\n * The remaining pods block until the lock is released, then proceed — Umzug will\n * see the migrations have already been applied and skip them.\n */\nconst runSadotMigrations = async (sequelize: Sequelize, options: MigratorOptions = {}): Promise<void> => {\n // Need to add a new migration? full migration guide here; https://www.notion.so/autofleet/How-to-Add-a-New-Migration-to-Sadot-36db603f40b98023b239d3c61aa22b00\n const queryInterface = sequelize.getQueryInterface() as unknown as QueryInterface;\n const storage = new SequelizeStorage({ sequelize: sequelize as never, tableName: 'sadot_migrations', modelName: 'SadotMigrations' });\n\n const umzugLogger = {\n info: (msg: Record<string, unknown>) => logger.info(`[sadot] ${JSON.stringify(msg)}`),\n warn: (msg: Record<string, unknown>) => logger.warn(`[sadot] ${JSON.stringify(msg)}`),\n error: (msg: Record<string, unknown>) => logger.error(`[sadot] ${JSON.stringify(msg)}`),\n debug: (msg: Record<string, unknown>) => logger.debug(`[sadot] ${JSON.stringify(msg)}`),\n };\n\n const migrator = new Umzug<QueryInterface>({\n migrations: buildMigrationList(options),\n context: queryInterface,\n storage,\n logger: umzugLogger,\n });\n\n const pending = await migrator.pending();\n if (pending.length === 0) {\n logger.info('[sadot] no pending migrations, skipping');\n return;\n }\n\n logger.info('[sadot] pending migrations detected', { pending: pending.map(m => m.name) });\n\n // The advisory lock holds a connection for its entire duration while\n // migrator.up() needs additional pool connections for queries. With\n // pool.max < 2 (common in test environments) this deadlocks.\n // Migrations are idempotent (IF NOT EXISTS / IF EXISTS) so running\n // without the lock is safe — just potentially duplicated work.\n const poolMax = (sequelize.options as any)?.pool?.max ?? 5;\n if (poolMax < 2) {\n logger.info('[sadot] connection pool too small for advisory lock, running migrations directly');\n await migrator.up();\n return;\n }\n\n // Use a dedicated raw connection (not a transaction) to hold the advisory lock.\n // A transaction connection sitting idle while migrator.up() runs on other pool\n // connections would be killed by idle_in_transaction_session_timeout, releasing\n // the lock prematurely and allowing a second replica to start migrations concurrently.\n logger.info('[sadot] acquiring advisory lock');\n const lockConnection = await (sequelize.connectionManager as any).getConnection({ type: 'write' });\n try {\n // Poll with pg_try_advisory_lock so the connection stays idle between attempts rather\n // than holding an active statement for the full wait duration. DDL operations that track\n // active backends between internal phases (e.g. CREATE INDEX CONCURRENTLY, VACUUM,\n // REINDEX CONCURRENTLY) wait for all active statements to finish before proceeding —\n // a long-blocking lock attempt would stall them for the entire wait.\n let lockAcquired = false;\n while (!lockAcquired) {\n const result = await lockConnection.query(`SELECT pg_try_advisory_lock(${SADOT_MIGRATION_LOCK_ID}) AS acquired`);\n lockAcquired = result.rows[0].acquired;\n if (!lockAcquired) await setTimeout(1000);\n }\n logger.info('[sadot] advisory lock acquired');\n\n // Re-check after acquiring the lock — another pod may have already\n // applied the migrations while we were waiting.\n const stillPending = await migrator.pending();\n if (stillPending.length > 0) {\n await migrator.up();\n } else {\n logger.info('[sadot] migrations already applied by another instance, skipping');\n }\n } finally {\n try {\n await lockConnection.query(`SELECT pg_advisory_unlock(${SADOT_MIGRATION_LOCK_ID})`);\n logger.info('[sadot] advisory lock released');\n } finally {\n (sequelize.connectionManager as any).releaseConnection(lockConnection);\n }\n }\n};\n\nexport { runSadotMigrations };\n"],"mappings":"gWAcA,KAAM,CAAE,QAAO,oBAAA,EAA6B,QAAQ,CAO9C,EAAsB,GAA8D,CACxF,GAAM,CAAE,yBAAyB,GAAO,sBAAsB,IAAU,EACxE,MAAO,CACL,CAAE,KAAM,yBAA8BA,KAAuBC,OAAmB,CAChF,GAAI,EAAyB,CAAC,CAAE,KAAM,kCAAmC,GAAIC,EAAiB,KAAMC,EAAmB,CAAC,CAAG,EAAE,CAC7H,GAAI,EAAsB,CAAC,CAAE,KAAM,yCAA0C,GAAIC,EAAiB,KAAMC,EAAmB,CAAC,CAAG,EAAE,CAClI,EAKG,EAA0B,UAY1B,EAAqB,MAAO,EAAsB,EAA2B,EAAE,GAAoB,CAEvG,IAAM,EAAiB,EAAU,mBAAmB,CAC9C,EAAU,IAAI,EAAiB,CAAa,YAAoB,UAAW,mBAAoB,UAAW,kBAAmB,CAAC,CAS9H,EAAW,IAAI,EAAsB,CACzC,WAAY,EAAmB,EAAQ,CACvC,QAAS,EACT,UACA,OAXkB,CAClB,KAAO,GAAiCC,EAAO,KAAK,WAAW,KAAK,UAAU,EAAI,GAAG,CACrF,KAAO,GAAiCA,EAAO,KAAK,WAAW,KAAK,UAAU,EAAI,GAAG,CACrF,MAAQ,GAAiCA,EAAO,MAAM,WAAW,KAAK,UAAU,EAAI,GAAG,CACvF,MAAQ,GAAiCA,EAAO,MAAM,WAAW,KAAK,UAAU,EAAI,GAAG,CACxF,CAOA,CAAC,CAEI,EAAU,MAAM,EAAS,SAAS,CACxC,GAAI,EAAQ,SAAW,EAAG,CACxB,EAAO,KAAK,0CAA0C,CACtD,OAWF,GARA,EAAO,KAAK,sCAAuC,CAAE,QAAS,EAAQ,IAAI,GAAK,EAAE,KAAK,CAAE,CAAC,EAOxE,EAAU,SAAiB,MAAM,KAAO,GAC3C,EAAG,CACf,EAAO,KAAK,mFAAmF,CAC/F,MAAM,EAAS,IAAI,CACnB,OAOF,EAAO,KAAK,kCAAkC,CAC9C,IAAM,EAAiB,MAAO,EAAU,kBAA0B,cAAc,CAAE,KAAM,QAAS,CAAC,CAClG,GAAI,CAMF,IAAI,EAAe,GACnB,KAAO,CAAC,GAEN,GADe,MAAM,EAAe,MAAM,+BAA+B,EAAwB,eAAe,EAC1F,KAAK,GAAG,SACzB,GAAc,MAAM,EAAW,IAAK,CAE3C,EAAO,KAAK,iCAAiC,EAIxB,MAAM,EAAS,SAAS,EAC5B,OAAS,EACxB,MAAM,EAAS,IAAI,CAEnB,EAAO,KAAK,mEAAmE,QAEzE,CACR,GAAI,CACF,MAAM,EAAe,MAAM,6BAA6B,EAAwB,GAAG,CACnF,EAAO,KAAK,iCAAiC,QACrC,CACP,EAAU,kBAA0B,kBAAkB,EAAe"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@autofleet/sadot",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.8.1-beta.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -54,9 +54,9 @@
|
|
|
54
54
|
"npm-watch": "^0.11.0",
|
|
55
55
|
"supertest": "^7.0.0",
|
|
56
56
|
"@autofleet/errors": "^3.3.13",
|
|
57
|
-
"@autofleet/
|
|
58
|
-
"@autofleet/
|
|
59
|
-
"@autofleet/
|
|
57
|
+
"@autofleet/logger": "^4.7.6",
|
|
58
|
+
"@autofleet/node-common": "^4.4.0",
|
|
59
|
+
"@autofleet/zehut": "^4.14.0"
|
|
60
60
|
},
|
|
61
61
|
"peerDependencies": {
|
|
62
62
|
"@autofleet/errors": "^3",
|