@creezio/platform-core
v0.26.0
Published
Paths userData / local-config schema paramétrés par brand-config (sans marque hardcodée)
Downloads
437
Readme
@creezio/platform-core
Rôle
@creezio/platform-core regroupe les primitives pures de plateforme Creezio : chemins userData, schéma local-config, split Client/Serveur, profils de connexion, layout SQLite multi-fichiers, runtime SQLite, migrations, embeds Hermes/n8n, plugins, télémétrie flotte, factory-reset, ports, licensing, préférences installeur et helpers d'env marque.
Le package est volontairement brand-agnostic. Il reçoit un AppManifest depuis @creezio/brand-config et ne doit pas connaître directement TempoFlow, Certivan ou Fidu. La majorité des modules sont testables depuis Node et ne doivent pas importer Electron.
Architecture SQLite portée par ce package :
core.dbsous{userData}/sqlite/core.dbpour les domaines plateforme ;brand.dbmétier sous{userData}/{manifest.dbFileName}viaresolveBrandDbPath;- DB plugin à l'installation sous
{userData}/sqlite/plugin/<id>.db; - table
_creezio_schema_migrationspropre à chaque fichier DB.
Périmètre (kit vs marque)
Ce qui appartient au kit
- Résolution de chemins à partir de
PathsContext:resolveUserDataDir,resolveDbPath,resolveBrandDbPath,resolveCoreDbPath,resolvePluginDbPath, etc.
- Split desktop Client/Serveur :
parseAppKind,resolveAppKind,readAppKindFile,userDataDirForAppKind,bootBehaviorFor,isAllowedServerCockpitPath.
- Config locale commune :
LOCAL_CONFIG_VERSION,LocalConfigFileV1,emptyLocalConfig,isLocalConfigV1.
- Runtime SQLite :
createSqliteRuntime,ensureDay0SqliteLayout,ensurePluginDb,ensureMigrations,composeMigrations.
- Contrats stores plateforme :
PLATFORM_STORES_CONTRACT,DEPRECATED_SHADOW_ONLY.
- Migrations plateforme :
platformCoreMigrations,PLATFORM_CORE_MIGRATION_IDS;platformHistoricalMigrations,runHistoricalMigrations,PLATFORM_HISTORICAL_STEP_VERSIONS.
- Embeds purs :
- Hermes : ports, env, résolution binaire, sanitize config, statut public ;
- n8n : ports, audit, résolution entry, env spawn/Next, statut public ;
- catalogue env embeds.
- Plugins purs :
- manifest, découverte, permissions, events, execution grants, site ids.
- Helpers transverses :
- ports/health, tunnel URLs, recovery key, licensing, installer prefs, disk-space, updater state, fleet telemetry, factory-reset.
Ce qui reste côté marque
- Les migrations métier brand.
- Les routes métier, repositories, services applicatifs et UI.
- Le choix du manifest et des chemins réels Electron (
app.getPath("userData"),process.resourcesPath). - Le chiffrement
safeStoragedesStoredValuedans le main Electron. - Le spawn concret des services (Next, Meili, Hermes, n8n, plugins), qui vit côté Electron/runtime.
- Les labels UI, libellés support et politiques produit propres à une marque.
Installation / build
Dans le monorepo :
npm run build -w @creezio/platform-core
npm run typecheck -w @creezio/platform-coreManifest package :
main:./dist-cjs/index.jsmodule:./dist/index.jstypes:./dist/index.d.ts- export public unique :
@creezio/platform-core
Dépendances :
- runtime :
@creezio/brand-config - peers optionnels :
@creezio/auth@creezio/database@creezio/product-hubbetter-sqlite3 >=9
- dev :
typescript,@types/node,@types/better-sqlite3
platformCoreMigrations() charge des SQL venant de peers optionnels au runtime. Ne pas transformer ces peers en imports statiques si cela crée des dépendances circulaires ou force des packages non présents.
Configuration (env, configure*, bindings)
PathsContext
La plupart des helpers de chemins reçoivent un PathsContext :
import { demobrandManifest } from "@creezio/brand-config";
import type { PathsContext } from "@creezio/platform-core";
const ctx: PathsContext = {
manifest: demobrandManifest,
userDataRoot: "/home/me/.config/demobrand",
isPackaged: false,
env: process.env,
resourcesRoot: process.cwd(),
};Champs :
manifest:AppManifestde la marque.userDataRoot: racine déjà résolue par Electron ou par un test.isPackaged: sitrue, les overrides d'env de chemins sont ignorés.env: injecté pour les tests ; défautprocess.env.resourcesRoot: racine des ressources packagées ou du repo en dev.
Overrides env
Les noms d'env sont dérivés via envKey(manifest, suffix) dans @creezio/brand-config.
Exemples pour TempoFlow :
TF2_USER_DATA_OVERRIDETF2_DB_PATH_OVERRIDETF2_BRAND_DB_PATH_OVERRIDETF2_SQLITE_ROOT_OVERRIDETF2_CORE_DB_PATH_OVERRIDETF2_PLUGIN_DB_DIR_OVERRIDETF2_NODE_BINARYTF2_N8N_BINTF2_HERMES_BIN
Les overrides de chemins sont lus uniquement hors packagé (isPackaged: false).
Env process Next / CRM
buildNextHostEnv construit l'env commun du process Next standalone :
import { buildNextHostEnv } from "@creezio/platform-core";
const env = buildNextHostEnv({
manifest,
port: 3000,
hostname: "127.0.0.1",
dbPath,
assistantDbPath,
uploadsDir,
meiliHost: "http://127.0.0.1:7700",
meiliMasterKey: "dev-key",
authSecret: "secret",
mcpJwtSecret: "jwt-secret",
extra: { CREEZIO_CORE_DB_PATH: coreDbPath },
});resolveCoreDbPathFromEnv() permet au process Next/CRM de retrouver core.db sans PathsContext :
CREEZIO_CORE_DB_PATH;- voisin de
DB_PATH({userData}/sqlite/core.db) ; /data/sqlite/core.dbsi/dataexiste ;null.
API publique (exports principaux avec exemples TS)
Version d'architecture
import { ARCHITECTURE_VERSION } from "@creezio/platform-core";
console.log(ARCHITECTURE_VERSION);ARCHITECTURE_VERSION décrit la phase d'architecture du kit et ne doit être bumpée qu'au sign-off de phase.
Chemins et ressources
import {
feedUrlForKind,
resolveAssistantDbPath,
resolveBrandDbPath,
resolveCoreDbPath,
resolveLocalConfigPath,
resolveLogsDir,
resolveMainLogPath,
resolveMeiliDataDir,
resolveN8nHomeDir,
resolveNodeRuntimeDir,
resolvePreloadPath,
resolveResourcesRoot,
resolveUploadsDir,
resolveUserDataDir,
userDataDirForKind,
} from "@creezio/platform-core";
const userData = resolveUserDataDir(ctx);
const brandDb = resolveBrandDbPath(ctx);
const coreDb = resolveCoreDbPath(ctx);
const config = resolveLocalConfigPath(ctx);
const log = resolveMainLogPath(ctx);
const preload = resolvePreloadPath(ctx, "preload.js");
const serverUserData = userDataDirForKind(manifest, "server", userData);
const feed = feedUrlForKind(manifest, "client");resolveDbPath(ctx) existe encore mais est déprécié : préférer resolveBrandDbPath(ctx).
Split Client/Serveur
import {
appKindEnvValue,
appKindFilePayload,
appUserModelIdFor,
bootBehaviorFor,
displayNameFor,
parseAppKind,
resolveAppKind,
userDataDirForAppKind,
} from "@creezio/platform-core";
parseAppKind("SERVER"); // "server"
const kind = resolveAppKind({
env: appKindEnvValue(manifest),
fileKind: "client",
});
const behavior = bootBehaviorFor(kind, {
mode: "server",
});
const targetUserData = userDataDirForAppKind(manifest, kind, currentUserData);
const appUserModelId = appUserModelIdFor(manifest, "server");
const displayName = displayNameFor(manifest, "client");
const payload = appKindFilePayload("client");bootBehaviorFor porte les règles :
server: stack locale autorisée, profil local forcé, cockpit/server-cockpit, pas de deep-link ;client: stack locale interdite, profil distant requis, pickerjoin-onlysauf join direct ;legacy: comportement tout-en-un compatible.
Config locale et profils de connexion
import {
assertProfileReady,
defaultLocalProfile,
emptyLocalConfig,
isLocalConfigV1,
normalizeRemoteUrl,
resolveBootProfile,
sanitizeConnectionProfile,
testRemoteHealth,
} from "@creezio/platform-core";
const cfg = emptyLocalConfig();
if (isLocalConfigV1(cfg)) {
cfg.connectionProfile = defaultLocalProfile();
}
const profile = sanitizeConnectionProfile({
mode: "remote",
remoteUrl: "crm.example.test",
chosen: true,
});
assertProfileReady(profile);
normalizeRemoteUrl("crm.example.test"); // "http://crm.example.test"
const boot = resolveBootProfile(profile);
const health = await testRemoteHealth(profile.remoteUrl!, 8000);Types notables :
LocalConfigFileV1StoredValueTunnelMetaStored,TunnelConfigPublicHermesEmbedConfig,N8nEmbedConfigBackgroundSettingsAiWorkspacePresentationSetting
Layout SQLite jour 0
import {
CORE_DB_FILENAME,
PLUGIN_DB_SUBDIR,
SQLITE_LAYOUT_DIR,
ensureDay0SqliteLayout,
ensurePluginDb,
pluginDbExists,
removePluginDb,
resolveDay0SqlitePaths,
resolvePluginDbPath,
resolveSqliteRoot,
} from "@creezio/platform-core";
const paths = resolveDay0SqlitePaths(ctx);
// { core: ".../sqlite/core.db", brand: ".../<manifest.dbFileName>" }
ensureDay0SqliteLayout(ctx, { touchBrand: true });
const plugin = ensurePluginDb(ctx, "meteo");
const exists = pluginDbExists(ctx, "meteo");
const pluginPath = resolvePluginDbPath(ctx, "meteo");
const removed = removePluginDb(ctx, "meteo");Un plugin DB est créé à l'installation, pas au boot jour 0.
Runtime SQLite et migrations
import {
composeMigrations,
createSqliteRuntime,
platformCoreMigrations,
type SqliteMigration,
} from "@creezio/platform-core";
const brandMigrations: SqliteMigration[] = [
{
id: "brand_001_catalog",
sql: "CREATE TABLE IF NOT EXISTS catalog_items (id TEXT PRIMARY KEY);",
},
];
const runtime = createSqliteRuntime({
ctx,
coreMigrations: platformCoreMigrations(),
brandMigrations: composeMigrations(brandMigrations),
});
runtime.getCore().exec("PRAGMA user_version;");
runtime.getBrand().prepare("SELECT 1").get();
const plugin = runtime.openPlugin("meteo", [
{ id: "meteo_001", sql: "CREATE TABLE IF NOT EXISTS events (id TEXT);" },
]);
console.log(plugin.created, runtime.status().openPlugins);
runtime.closePlugin("meteo");
runtime.close();Exports associés :
SqliteRuntime,SqliteHandle,SqliteLayerRef,SqliteLayerKind;ensureMigrations,listAppliedMigrations,SQLITE_MIGRATIONS_TABLE,SQLITE_META_MIGRATION;openNodeSqliteDatabasepournode:sqlite/driver compatible ;platformHistoricalMigrations,runHistoricalMigrationspour les anciennes migrationsbrand.dbàschema_version.
Important : le runner historique runHistoricalMigrations est prévu pour un process Node vanilla, pas pour le main Electron chargé avec un ABI différent de better-sqlite3.
Stores plateforme
import {
DEPRECATED_SHADOW_ONLY,
PLATFORM_STORES_CONTRACT,
} from "@creezio/platform-core";
console.log(PLATFORM_STORES_CONTRACT);
console.log(DEPRECATED_SHADOW_ONLY);Ce contrat fige le store SoT de chaque domaine plateforme dans core.db et interdit tout dual-write runtime.
Embeds Hermes et n8n
import {
HERMES_DESKTOP_API_PORT,
HERMES_DESKTOP_WEBUI_PORT,
N8N_DESKTOP_PORT,
buildNextHermesEnv,
buildN8nSpawnEnv,
hermesBinEnvKey,
n8nBinEnvKey,
resolveHermesBinary,
resolveN8nEntry,
sanitizeHermesEmbedConfig,
sanitizeN8nEmbedConfig,
shouldSpawnEmbeddedHermes,
shouldSpawnEmbeddedN8n,
} from "@creezio/platform-core";
const hermes = sanitizeHermesEmbedConfig(null);
const n8n = sanitizeN8nEmbedConfig(null);
shouldSpawnEmbeddedHermes({ connectionMode: "local", hermes });
shouldSpawnEmbeddedN8n({ connectionMode: "local", n8n });
const hermesKey = hermesBinEnvKey(manifest);
const n8nKey = n8nBinEnvKey(manifest);
const hermesEnv = buildNextHermesEnv({
apiUrl: `http://127.0.0.1:${HERMES_DESKTOP_API_PORT}`,
apiKey: "local-key",
webuiUrl: `http://127.0.0.1:${HERMES_DESKTOP_WEBUI_PORT}`,
});Le catalogue d'env embeds expose :
N8N_ENV_CATALOG,HERMES_ENV_CATALOG;N8N_LOCKED_KEYS,HERMES_LOCKED_KEYS,OS_SANDBOX_LOCKED_KEYS;catalogFor,lockedKeySet,sanitizeUserEnvOverlay,mergeEmbedUserEnv,buildEmbedEnvPanel.
Plugins purs
import {
discoverPlugins,
hasPluginPermission,
isValidPluginId,
parsePluginManifest,
pluginEnabledFlagPath,
pluginHookUrl,
pluginRuntimePath,
pluginsRootDir,
setPluginEnabled,
} from "@creezio/platform-core";
const root = pluginsRootDir(resolveUserDataDir(ctx));
const plugins = discoverPlugins(root);
const manifest = parsePluginManifest({
id: "meteo",
name: "Meteo",
version: "1.0.0",
main: "index.js",
permissions: ["crm:read", "ui:panel"],
});
if (hasPluginPermission(manifest, "ui:panel")) {
setPluginEnabled(root, "meteo", true);
}Autres exports plugins :
- events :
PLUGIN_RUNTIME_FILE,pluginAcceptsHook,pluginN8nWebhookUrl,pluginSiteId,readPluginRuntimeState,writePluginRuntimeState; - grants :
issuePluginExecutionGrant,verifyPluginExecutionGrant; - manifest :
PLUGIN_MANIFEST_FILE,PluginPermission,PluginManifest,DiscoveredPlugin.
Recovery key, licensing, installer prefs, fleet, ports
import {
applyFleetTelemetryPatch,
checkLicense,
consumeInstallerPrefsFile,
defaultFleetTelemetry,
findFreePort,
generateRecoveryKey,
initialUpdateStatus,
installerPrefsPath,
reduceUpdateEvent,
storeLicenseKey,
waitForHealth,
} from "@creezio/platform-core";
const key = generateRecoveryKey();
const telemetry = applyFleetTelemetryPatch(defaultFleetTelemetry(), {
preset: "basic",
});
const port = await findFreePort("127.0.0.1", 3000);
const status = reduceUpdateEvent(initialUpdateStatus("0.1.0"), {
type: "checking",
});Ces helpers restent purs ou Node-only, et ne doivent pas dépendre d'Electron.
Flux / fonctionnement
Boot desktop packagé
- Le main Electron choisit le manifest (
@creezio/brand-config). - Il résout le kind avec
resolveAppKindà partir de l'env et/ouapp-kind.json. - Il calcule le
userDatacible viauserDataDirForAppKindet configure Electron. - Il construit un
PathsContext. - Il résout chemins, config locale, logs, DBs et ressources via les helpers.
- Le serveur local ou client distant applique
bootBehaviorFor.
Boot serveur / SQLite
ensureDay0SqliteLayout(ctx, { touchBrand: true })garantitcore.dbetbrand.db.createSqliteRuntimeouvrecore+brand.- Les migrations core plateforme viennent de
platformCoreMigrations(). - Les migrations métier sont injectées via
brandMigrations. - Les plugins ne sont ouverts qu'à l'installation avec
openPlugin(pluginId, migrations).
Process Next / CRM
- Le launcher injecte
DB_PATH,ASSISTANT_DB_PATH,UPLOADS_DIR,CREEZIO_CORE_DB_PATH, etc. - Côté Next,
resolveCoreDbPathFromEnvpermet de retrouvercore.db. - Les routes plateforme consomment
core.db; le métier marque vit dansbrand.db.
Intégration marques (H11)
Le kit ne publie plus les manifests prod. Une marque fournit son
AppManifest (JSON local / resolveManifest). brandEnv /
buildNextHostEnv n'injectent que ${envPrefix}_*. Feature-off
(plugins / fleet = false) se déclare sur le manifest, pas via un
export kit.
DemoBrand
- Manifest sandbox généré par
createAppManifest. - Préfixe env :
DEMOBRAND. - DB brand :
demobrand.db. - Config :
demobrand-config.json. - Utilisé par factory et tests sans secrets production.
- Sert à vérifier que les helpers sont réellement brand-agnostic.
Dépendances @creezio/*
- Dépendance runtime :
@creezio/brand-configpourAppManifest,envKey, feeds et identités.
- Peers optionnels :
@creezio/authpour les migrations core auth ;@creezio/product-hubpour les migrations Product Hub ;@creezio/databaseselon les intégrations ;better-sqlite3pour certains runners historiques ou injections driver.
- Consommateurs fréquents :
@creezio/api-kernelpourSqliteRuntime, scoped DB etARCHITECTURE_VERSION;@creezio/electron-shellpour le main Electron ;@creezio/shell-ui,@creezio/onboarding,@creezio/cockpitpour des contrats UI/config ;- packages domaine (
auth,tasks,mails,assistant,automations,observability, etc.) pour les chemins et contrats plateforme.
