diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index c6a97c2e6..5701aaa77 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -84,6 +84,7 @@ module.exports = {
'@typescript-eslint/no-unsafe-member-access': 'warn',
'@typescript-eslint/no-unsafe-return': 'warn',
'@typescript-eslint/no-unsafe-assignment': 'warn',
+ '@typescript-eslint/no-unused-vars': 'warn',
'@typescript-eslint/member-delimiter-style': [
'error',
diff --git a/site/build/vite-build-simulation-data.config.ts b/site/build/vite-build-simulation-data.config.ts
index 314fb078a..4e658facd 100644
--- a/site/build/vite-build-simulation-data.config.ts
+++ b/site/build/vite-build-simulation-data.config.ts
@@ -3,7 +3,7 @@ import { join, resolve } from 'path'
import { defineConfig } from 'vite'
-import { PageConfig } from '@/pages/simulateurs/_configs/types'
+import type { PageConfig } from '@/pages/simulateurs/_configs/types'
import { objectTransform } from '../source/utils'
diff --git a/site/source/components/App.tsx b/site/source/components/App.tsx
index dc8df2d7b..c3808a0f2 100644
--- a/site/source/components/App.tsx
+++ b/site/source/components/App.tsx
@@ -1,33 +1,23 @@
import { ErrorBoundary } from '@sentry/react'
import { FallbackRender } from '@sentry/react/types/errorboundary'
-import rules from 'modele-social'
-import { ComponentProps, StrictMode, useMemo } from 'react'
+import { ComponentProps, StrictMode, useEffect, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Route, Routes } from 'react-router-dom'
import { css, styled } from 'styled-components'
import Footer from '@/components/layout/Footer/Footer'
import Header from '@/components/layout/Header'
-import {
- engineFactory,
- EngineProvider,
- Rules,
- useEngine,
- useSetupSafeSituation,
-} from '@/components/utils/EngineContext'
import { Container } from '@/design-system/layout'
import { useAxeCoreAnalysis } from '@/hooks/useAxeCoreAnalysis'
import { useGetFullURL } from '@/hooks/useGetFullURL'
import { useIsEmbedded } from '@/hooks/useIsEmbedded'
+import { useLazyPromise } from '@/hooks/usePromise'
import { useSaveAndRestoreScrollPosition } from '@/hooks/useSaveAndRestoreScrollPosition'
import Landing from '@/pages/_landing/Landing'
import Page404 from '@/pages/404'
import Accessibilité from '@/pages/Accessibilité'
-import Assistants from '@/pages/assistants/index'
import Budget from '@/pages/budget/index'
import IntegrationTest from '@/pages/dev/IntegrationTest'
-import Documentation from '@/pages/Documentation'
-import Iframes from '@/pages/iframes'
import Integration from '@/pages/integration/index'
import Nouveautés from '@/pages/nouveautés/index'
import Offline from '@/pages/Offline'
@@ -36,53 +26,274 @@ import Simulateurs from '@/pages/simulateurs'
import SimulateursEtAssistants from '@/pages/simulateurs-et-assistants'
import Stats from '@/pages/statistiques/LazyStats'
import { useSitePaths } from '@/sitePaths'
+import {
+ useAsyncGetRule,
+ useAsyncParsedRules,
+ useLazyPromiseOnSituationChange,
+ usePromiseOnSituationChange,
+ useShallowCopy,
+ useWorkerEngine,
+} from '@/worker/socialWorkerEngineClient'
import Provider, { ProviderProps } from './Provider'
import Redirections from './Redirections'
type RootProps = {
basename: ProviderProps['basename']
- rulesPreTransform?: (rules: Rules) => Rules
+ // rulesPreTransform?: (rules: Rules) => Rules
+}
+
+const TestWorkerEngine = () => {
+ const [refresh, setRefresh] = useState(0)
+ const workerEngine = useWorkerEngine()
+ // const workerEngineCtx = useWorkerEngineContext()
+
+ const [, trigger] = useLazyPromise(
+ async () =>
+ workerEngine?.asyncSetSituationWithEngineId({
+ SMIC: '1000€/mois',
+ }),
+ [workerEngine],
+ { defaultValue: 'loading...' }
+ )
+
+ const date = useAsyncGetRule('date', { defaultValue: 'loading...' })
+
+ const parsedRules = useAsyncParsedRules()
+
+ const resultSmic = usePromiseOnSituationChange(
+ () => workerEngine.asyncEvaluateWithEngineId('SMIC'),
+ [workerEngine],
+ { defaultValue: 'loading...' }
+ )
+
+ const [resultLazySmic, triggerLazySmic] = useLazyPromiseOnSituationChange(
+ () => workerEngine.asyncEvaluateWithEngineId('SMIC'),
+ [workerEngine],
+ { defaultValue: 'wait 2sec...' }
+ )
+
+ useEffect(() => {
+ console.log('??? useEffect')
+
+ void (async () => {
+ await workerEngine.isWorkerReady
+ setTimeout(() => {
+ void triggerLazySmic()
+ }, 3000)
+ })()
+ }, [triggerLazySmic, workerEngine.isWorkerReady])
+
+ const workerEngineCopy = useShallowCopy(workerEngine)
+ // // const workerEngineCopy = workerEngine
+ console.log('=========>', workerEngine, workerEngineCopy)
+
+ const [, triggerCopy] = useLazyPromise(async () => {
+ // console.log('+++++++++>', workerEngineCopy)
+
+ await workerEngineCopy?.asyncSetSituationWithEngineId({
+ SMIC: '2000€/mois',
+ })
+ }, [workerEngineCopy])
+
+ const dateCopy = useAsyncGetRule('date', {
+ defaultValue: 'loading...',
+ // workerEngine: workerEngineCopy,
+ })
+
+ const parsedRulesCopy = useAsyncParsedRules({
+ workerEngine: workerEngineCopy,
+ })
+
+ const resultSmicCopy = usePromiseOnSituationChange(
+ async () => workerEngineCopy?.asyncEvaluateWithEngineId('SMIC'),
+ [workerEngineCopy],
+ {
+ defaultValue: 'loading...',
+ workerEngine: workerEngineCopy,
+ }
+ )
+
+ const [resultLazySmicCopy, triggerLazySmicCopy] =
+ useLazyPromiseOnSituationChange(
+ async () => workerEngineCopy?.asyncEvaluateWithEngineId('SMIC'),
+ [workerEngineCopy],
+ {
+ defaultValue: 'wait 2sec...',
+ workerEngine: workerEngineCopy,
+ }
+ )
+
+ useEffect(() => {
+ // console.log('useEffect')
+
+ void (async () => {
+ await workerEngine.isWorkerReady
+ setTimeout(() => {
+ void triggerLazySmicCopy()
+ }, 3000)
+ })()
+ }, [triggerLazySmicCopy, workerEngine.isWorkerReady])
+
+ const { asyncSetSituationWithEngineId } = workerEngineCopy ?? {}
+ usePromiseOnSituationChange(async () => {
+ // console.log('**************>', workerEngineCopy, resultSmic)
+
+ if (
+ typeof resultSmic !== 'string' &&
+ typeof resultSmic.nodeValue === 'number'
+ ) {
+ // console.log('ooooooooooooooooooo', resultSmic)
+
+ await asyncSetSituationWithEngineId?.({
+ SMIC: resultSmic.nodeValue + '€/mois',
+ })
+ }
+ }, [asyncSetSituationWithEngineId, resultSmic])
+
+ return (
+
+
Test worker engine
+
+
+
+
+
+ date title:{' '}
+ {JSON.stringify(typeof date === 'string' ? date : date?.title)}
+
+
+ parsedRules length:{' '}
+ {JSON.stringify(Object.entries(parsedRules ?? {}).length)}
+
+
+ resultSmic:{' '}
+ {JSON.stringify(
+ typeof resultSmic === 'string' ? resultSmic : resultSmic?.nodeValue
+ )}
+
+
+ resultLazySmic:{' '}
+ {JSON.stringify(
+ typeof resultLazySmic === 'string'
+ ? resultLazySmic
+ : resultLazySmic?.nodeValue
+ )}
+
+
+
workerEngineCopy: {JSON.stringify(workerEngineCopy?.engineId)}
+
+
+ dateCopy title:{' '}
+ {JSON.stringify(
+ typeof dateCopy === 'string' ? dateCopy : dateCopy?.title
+ )}
+
+
+ parsedRulesCopy length:{' '}
+ {JSON.stringify(Object.entries(parsedRulesCopy ?? {}).length)}
+
+
+ resultSmicCopy:{' '}
+ {JSON.stringify(
+ typeof resultSmicCopy === 'string'
+ ? resultSmicCopy
+ : resultSmicCopy?.nodeValue
+ )}
+
+
+ resultLazySmicCopy:{' '}
+ {JSON.stringify(
+ typeof resultLazySmicCopy === 'string'
+ ? resultLazySmicCopy
+ : resultLazySmicCopy?.nodeValue
+ )}
+
+
+ )
}
export default function Root({
basename,
- rulesPreTransform = (r) => r,
-}: RootProps) {
- const engine = useMemo(
- () => engineFactory(rulesPreTransform(rules)),
+}: // rulesPreTransform = (r) => r,
+RootProps) {
+ // const situationVersion = useCreateWorkerEngine(basename)
+ // const engine = useMemo(
+ // () => engineFactory(rulesPreTransform(rules)),
- // We need to keep [rules] in the dependency list for hot reload of the rules
- // in dev mode, even if ESLint think it is unnecessary since `rules` isn't
- // defined in the component scope.
- //
- // eslint-disable-next-line react-hooks/exhaustive-deps
- [rules]
- )
+ // // We need to keep [rules] in the dependency list for hot reload of the rules
+ // // in dev mode, even if ESLint think it is unnecessary since `rules` isn't
+ // // defined in the component scope.
+ // //
+ // // eslint-disable-next-line react-hooks/exhaustive-deps
+ // [rules]
+ // )
return (
-
-
-
-
-
-
-
+ {/* */}
+
+
+
+
+
+ {/* */}
)
}
-
const Router = () => {
- const engine = useEngine()
+ /*
+ const exampleSyncValue = usePromiseOnSituationChange(
+ () => asyncEvaluate('SMIC'),
+ []
+ )?.nodeValue
- useSetupSafeSituation(engine)
+ const exampleSyncValueWithDefault = usePromiseOnSituationChange(
+ async () => (await asyncEvaluate('SMIC')).nodeValue,
+ [],
+ 'loading...'
+ )
+
+ const [exampleAsyncValue, fireEvaluate] = useLazyPromise(
+ async (param: PublicodesExpression) =>
+ (await asyncEvaluate(param)).nodeValue,
+ [],
+ 42
+ )
+
+ usePromise(async () => {
+ let count = 0
+ const interval = setInterval(() => {
+ void fireEvaluate(count++ % 2 === 0 ? 'date' : 'SMIC')
+ if (count === 7) clearInterval(interval)
+ }, 1000)
+
+ await new Promise((resolve) => setTimeout(resolve, 3000))
+ await asyncSetSituation({ date: '01/01/2022' })
+ await new Promise((resolve) => setTimeout(resolve, 3000))
+ await asyncSetSituation({ date: '01/01/2021' })
+ await new Promise((resolve) => setTimeout(resolve, 3000))
+ }, [fireEvaluate])
+
+ */
return (
-
- } />
- } />
-
+ <>
+ {/* exemple sans valeur par defaut : {JSON.stringify(exampleSyncValue)}
+
+ exemple avec valeur par defaut :{' '}
+ {JSON.stringify(exampleSyncValueWithDefault)}
+ exemple d'execution manuel : {JSON.stringify(exampleAsyncValue)} */}
+ {/* */}
+
+ } />
+
+ {/* } /> */}
+ } />
+
+ >
)
}
@@ -96,11 +307,8 @@ const CatchOffline = ({ error }: ComponentProps) => {
const App = () => {
const { relativeSitePaths } = useSitePaths()
-
const { t } = useTranslation()
-
const fullURL = useGetFullURL()
-
useSaveAndRestoreScrollPosition()
const isEmbedded = useIsEmbedded()
if (!import.meta.env.PROD && import.meta.env.VITE_AXE_CORE_ENABLED) {
@@ -108,7 +316,6 @@ const App = () => {
useAxeCoreAnalysis()
}
const documentationPath = useSitePaths().absoluteSitePaths.documentation.index
- const engine = useEngine()
return (
@@ -137,10 +344,10 @@ const App = () => {
} />
- }
- />
+ /> */}
}
@@ -149,15 +356,15 @@ const App = () => {
path={relativeSitePaths.simulateursEtAssistants + '/*'}
element={}
/>
-
- }
- />
+ {/*
+ }
+ /> */}
}
@@ -172,12 +379,10 @@ const App = () => {
path={relativeSitePaths.accessibilité}
element={}
/>
-
}
/>
-
} />
} />
@@ -185,7 +390,6 @@ const App = () => {
-
{!isEmbedded && }
)
diff --git a/site/source/components/ChiffreAffairesActivitéMixte.tsx b/site/source/components/ChiffreAffairesActivitéMixte.tsx
index 293ff24cf..c53ca5c1f 100644
--- a/site/source/components/ChiffreAffairesActivitéMixte.tsx
+++ b/site/source/components/ChiffreAffairesActivitéMixte.tsx
@@ -6,15 +6,21 @@ import { useDispatch, useSelector } from 'react-redux'
import { styled } from 'styled-components'
import { Switch } from '@/design-system/switch'
+import { useLazyPromise } from '@/hooks/usePromise'
import { batchUpdateSituation } from '@/store/actions/actions'
import { situationSelector } from '@/store/selectors/simulationSelectors'
+import { ReplaceReturnType } from '@/types/utils'
import { catchDivideByZeroError } from '@/utils'
+import {
+ useAsyncGetRule,
+ usePromiseOnSituationChange,
+ useWorkerEngine,
+} from '@/worker/socialWorkerEngineClient'
import { ExplicableRule } from './conversation/Explicable'
import { Condition, WhenApplicable } from './EngineValue'
import { SimulationGoal } from './Simulation'
import { FromTop } from './ui/animate'
-import { useEngine } from './utils/EngineContext'
const proportions = {
'entreprise . activités . revenus mixtes . proportions . service BIC':
@@ -60,7 +66,12 @@ export default function ChiffreAffairesActivitéMixte({
,
+ void
+ >
+ }
dottedName={chiffreAffaires}
/>
))}
@@ -72,11 +83,11 @@ export default function ChiffreAffairesActivitéMixte({
}
function useAdjustProportions(CADottedName: DottedName) {
- const engine = useEngine()
const dispatch = useDispatch()
+ const workerEngine = useWorkerEngine()
- return useCallback(
- (name: DottedName, value?: PublicodesExpression) => {
+ const [, trigger] = useLazyPromise(
+ async (name: DottedName, value?: PublicodesExpression) => {
const checkValue = (
val: unknown
): val is { valeur: number; unité: string } =>
@@ -87,61 +98,79 @@ function useAdjustProportions(CADottedName: DottedName) {
typeof val.valeur === 'number' &&
typeof val.unité === 'string'
- const old = Object.values(proportions).map((chiffreAffaire) =>
- serializeEvaluation(
- engine.evaluate(
- name === chiffreAffaire && checkValue(value)
- ? value
- : chiffreAffaire
+ const old = await Promise.all(
+ Object.values(proportions).map(async (chiffreAffaire) =>
+ serializeEvaluation(
+ await workerEngine.asyncEvaluateWithEngineId(
+ name === chiffreAffaire && checkValue(value)
+ ? value
+ : chiffreAffaire
+ )
)
)
)
const nouveauCA = serializeEvaluation(
- engine.evaluate({ somme: old.filter(Boolean) })
+ await workerEngine.asyncEvaluateWithEngineId({
+ somme: old.filter(Boolean),
+ })
)
if (nouveauCA === '0€/an') {
return // Avoid division by 0
}
- const situation = Object.entries(proportions).reduce(
- (acc, [proportionName, valueName]) => {
+
+ const entries = Object.entries(proportions).map(
+ async ([proportionName, valueName]) => {
const newValue = serializeEvaluation(
- engine.evaluate(
+ await workerEngine.asyncEvaluateWithEngineId(
valueName === name && checkValue(value)
? value
: { valeur: valueName, 'par défaut': '0€/an' }
)
)
const newProportion = serializeEvaluation(
- catchDivideByZeroError(() =>
- engine.evaluate({
+ await catchDivideByZeroError(() =>
+ workerEngine.asyncEvaluateWithEngineId({
valeur: `${newValue ?? ''} / ${nouveauCA ?? ''}`,
unité: '%',
})
)
)
- return {
- ...acc,
- [proportionName]: newProportion,
- [valueName]: undefined,
- }
- },
+ return [proportionName, valueName, newProportion] as const
+ }
+ )
+
+ const situation = (await Promise.all(entries)).reduce(
+ (acc, [proportionName, valueName, newProportion]) => ({
+ ...acc,
+ [proportionName]: newProportion,
+ [valueName]: undefined,
+ }),
{ [CADottedName]: nouveauCA }
)
dispatch(batchUpdateSituation(situation))
},
- [CADottedName, engine, dispatch]
+ [CADottedName, dispatch, workerEngine]
)
+
+ return trigger
}
function ActivitéMixte() {
const dispatch = useDispatch()
const situation = useSelector(situationSelector)
- const rule = useEngine().getRule('entreprise . activités . revenus mixtes')
+ const rule = useAsyncGetRule('entreprise . activités . revenus mixtes')
+ const workerEngine = useWorkerEngine()
const defaultChecked =
- useEngine().evaluate('entreprise . activités . revenus mixtes')
- .nodeValue === true
+ usePromiseOnSituationChange(
+ () =>
+ workerEngine.asyncEvaluateWithEngineId(
+ 'entreprise . activités . revenus mixtes'
+ ),
+ [workerEngine]
+ )?.nodeValue === true
+
const onMixteChecked = useCallback(
(checked: boolean) => {
dispatch(
@@ -173,7 +202,7 @@ function ActivitéMixte() {
Activité mixte
-
+ {rule && }
)
diff --git a/site/source/components/EngineValue.tsx b/site/source/components/EngineValue.tsx
index 52abf27e6..d91e1f960 100644
--- a/site/source/components/EngineValue.tsx
+++ b/site/source/components/EngineValue.tsx
@@ -1,5 +1,5 @@
import { DottedName } from 'modele-social'
-import Engine, {
+import {
ASTNode,
EvaluatedNode,
formatValue,
@@ -10,13 +10,21 @@ import React from 'react'
import { useTranslation } from 'react-i18next'
import { keyframes, styled } from 'styled-components'
+import {
+ useAsyncParsedRules,
+ usePromiseOnSituationChange,
+ useWorkerEngine,
+} from '@/worker/socialWorkerEngineClient'
+
import RuleLink from './RuleLink'
-import { useEngine } from './utils/EngineContext'
+
+// import { useEngine } from './utils/EngineContext'
export type ValueProps = {
expression: PublicodesExpression
unit?: string
- engine?: Engine
+ // engine?: Engine
+ engineId?: number
displayedUnit?: string
precision?: number
documentationPath?: string
@@ -27,7 +35,7 @@ export type ValueProps = {
export default function Value({
expression,
unit,
- engine,
+ engineId = 0,
displayedUnit,
flashOnChange = false,
precision,
@@ -36,27 +44,41 @@ export default function Value({
...props
}: ValueProps) {
const { language } = useTranslation().i18n
+ const workerEngine = useWorkerEngine()
if (expression === null) {
throw new TypeError('expression cannot be null')
}
- const defaultEngine = useEngine()
- const e = engine ?? defaultEngine
- const isRule =
- typeof expression === 'string' && expression in e.getParsedRules()
-
- const evaluation = e.evaluate({
- valeur: expression,
- ...(unit && { unité: unit }),
+ const parsedRules = useAsyncParsedRules({
+ workerEngine,
})
+ const isRule =
+ typeof expression === 'string' && parsedRules && expression in parsedRules
+
+ const evaluation = usePromiseOnSituationChange(
+ () =>
+ workerEngine.asyncEvaluateWithEngineId({
+ valeur: expression,
+ ...(unit && { unité: unit }),
+ }),
+ [expression, unit, workerEngine]
+ )
+
const value = formatValue(evaluation, {
displayedUnit,
language,
precision,
}) as string
- if (isRule && linkToRule) {
- const ruleEvaluation = e.evaluate(expression)
+ const ruleEvaluation = usePromiseOnSituationChange(
+ async () =>
+ isRule &&
+ linkToRule &&
+ workerEngine.asyncEvaluateWithEngineId(expression),
+ [expression, isRule, linkToRule, workerEngine]
+ )
+
+ if (isRule && linkToRule && ruleEvaluation) {
let dottedName = expression as DottedName
if (ruleEvaluation.sourceMap?.mecanismName === 'replacement') {
dottedName =
@@ -88,17 +110,16 @@ export default function Value({
)
}
-const flash = keyframes`
+const flash = keyframes`
from {
background-color: white;
opacity: 0.8;
}
- to {
- background-color: transparent;
- }
-
+ to {
+ background-color: transparent;
+ }
`
const StyledValue = styled.span<{ $flashOnChange: boolean }>`
@@ -109,118 +130,168 @@ const StyledValue = styled.span<{ $flashOnChange: boolean }>`
type ConditionProps = {
expression: PublicodesExpression | ASTNode
children: React.ReactNode
- engine?: Engine
+ // engine?: Engine
+ engineId?: number
}
export function Condition({
expression,
children,
- engine: engineFromProps,
+ // engine: engineFromProps,
+ engineId = 0,
}: ConditionProps) {
- const defaultEngine = useEngine()
- const engine = engineFromProps ?? defaultEngine
- const nodeValue = engine.evaluate({ '!=': [expression, 'non'] }).nodeValue
+ // const defaultEngine = useEngine()
+ // const engine = engineFromProps ?? defaultEngine
+ // const nodeValue = engine.evaluate({ '!=': [expression, 'non'] }).nodeValue
- if (!nodeValue) {
- return null
- }
+ // if (!nodeValue) {
+ // return null
+ // }
- return <>{children}>
+ // return <>{children}>
+
+ const workerEngine = useWorkerEngine()
+
+ const node = usePromiseOnSituationChange(
+ () =>
+ workerEngine.asyncEvaluateWithEngineId({
+ '!=': [expression, 'non'],
+ }),
+ [expression, workerEngine]
+ )
+
+ return !node?.nodeValue ? null : <>{children}>
}
export function WhenValueEquals({
expression,
value,
children,
- engine: engineFromProps,
+ // engine: engineFromProps,
+ engineId = 0,
}: ConditionProps & { value: string | number }) {
- const defaultEngine = useEngine()
- const engine = engineFromProps ?? defaultEngine
- const nodeValue = engine.evaluate(expression).nodeValue
+ // const defaultEngine = useEngine()
+ // const engine = engineFromProps ?? defaultEngine
+ // const nodeValue = engine.evaluate(expression).nodeValue
- if (nodeValue !== value) {
- return null
- }
+ // if (nodeValue !== value) {
+ // return null
+ // }
- return <>{children}>
+ // return <>{children}>
+ const workerEngine = useWorkerEngine()
+
+ const node = usePromiseOnSituationChange(
+ () => workerEngine.asyncEvaluateWithEngineId(expression),
+ [expression, workerEngine]
+ )
+
+ return node?.nodeValue !== value ? null : <>{children}>
}
export function WhenApplicable({
dottedName,
children,
- engine,
+ engineId = 0,
}: {
dottedName: DottedName
children: React.ReactNode
- engine?: Engine
+ // engine?: Engine
+ engineId?: number
}) {
- const defaultEngine = useEngine()
+ const workerEngine = useWorkerEngine()
+ // const defaultEngine = useEngine()
- const engineValue = engine ?? defaultEngine
+ // const engineValue = engine ?? defaultEngine
- if (
- engineValue.evaluate({ 'est applicable': dottedName }).nodeValue !== true
- ) {
- return null
- }
+ // if (
+ // engineValue.evaluate({ 'est applicable': dottedName }).nodeValue !== true
+ // ) {
+ // return null
+ // }
- return <>{children}>
+ // return <>{children}>
+
+ const node = usePromiseOnSituationChange(
+ () =>
+ workerEngine.asyncEvaluateWithEngineId({
+ 'est applicable': dottedName,
+ }),
+ [dottedName, workerEngine]
+ )
+
+ return node?.nodeValue !== true ? <>{children}> : null
}
export function WhenNotApplicable({
dottedName,
children,
- engine,
+ engineId = 0,
}: {
dottedName: DottedName
children: React.ReactNode
- engine?: Engine
+ // engine?: Engine
+ engineId?: number
}) {
- const defaultEngine = useEngine()
+ // const defaultEngine = useEngine()
- const engineValue = engine ?? defaultEngine
+ // const engineValue = engine ?? defaultEngine
- if (
- engineValue.evaluate({ 'est non applicable': dottedName }).nodeValue !==
- true
- ) {
- return null
- }
+ // if (
+ // engineValue.evaluate({ 'est non applicable': dottedName }).nodeValue !==
+ // true
+ // ) {
+ // return null
+ // }
- return <>{children}>
+ // return <>{children}>
+ const workerEngine = useWorkerEngine()
+
+ const node = usePromiseOnSituationChange(
+ () =>
+ workerEngine.asyncEvaluateWithEngineId({
+ 'est non applicable': dottedName,
+ }),
+ [dottedName, workerEngine]
+ )
+
+ return node?.nodeValue !== true ? null : <>{children}>
}
export function WhenAlreadyDefined({
dottedName,
children,
- engine,
+ engineId = 0,
}: {
dottedName: DottedName
children: React.ReactNode
- engine?: Engine
+ // engine?: Engine
+ engineId?: number
}) {
- const defaultEngine = useEngine()
+ const workerEngine = useWorkerEngine()
+ const node = usePromiseOnSituationChange(
+ () =>
+ workerEngine.asyncEvaluateWithEngineId({ 'est non défini': dottedName }),
+ [dottedName, workerEngine]
+ )
- const engineValue = engine ?? defaultEngine
-
- if (engineValue.evaluate({ 'est non défini': dottedName }).nodeValue) {
- return null
- }
-
- return <>{children}>
+ return node?.nodeValue ? null : <>{children}>
}
export function WhenNotAlreadyDefined({
dottedName,
children,
+ engineId = 0,
}: {
dottedName: DottedName
children: React.ReactNode
+ engineId?: number
}) {
- const engine = useEngine()
- if (engine.evaluate({ 'est défini': dottedName }).nodeValue) {
- return null
- }
+ const workerEngine = useWorkerEngine()
+ const node = usePromiseOnSituationChange(
+ () => workerEngine.asyncEvaluateWithEngineId({ 'est défini': dottedName }),
+ [dottedName, workerEngine]
+ )
- return <>{children}>
+ return node?.nodeValue ? null : <>{children}>
}
diff --git a/site/source/components/Feedback/index.tsx b/site/source/components/Feedback/index.tsx
index 5829b7418..8bc64d0ea 100644
--- a/site/source/components/Feedback/index.tsx
+++ b/site/source/components/Feedback/index.tsx
@@ -15,6 +15,15 @@ const FeedbackButton = ({ isEmbedded }: { isEmbedded?: boolean }) => {
const { t } = useTranslation()
const containerRef = useRef(null)
const [feedbackFormIsOpened, setFeedbackFormIsOpened] = useState(false)
+
+ // const { absoluteSitePaths } = useSitePaths()
+ // const currentPath = useLocation().pathname
+ // const isSimulateurSalaire =
+ // currentPath.includes(absoluteSitePaths.simulateurs.salarié) ||
+ // currentPath.includes(IFRAME_SIMULATEUR_EMBAUCHE_PATH)
+
+ // const { shouldShowRater, customTitle } = useFeedback()
+
useOnClickOutside(
containerRef,
() => !feedbackFormIsOpened && setIsFormOpen(false)
diff --git a/site/source/components/Notifications.tsx b/site/source/components/Notifications.tsx
index 22f899cb0..e05532c58 100644
--- a/site/source/components/Notifications.tsx
+++ b/site/source/components/Notifications.tsx
@@ -4,12 +4,18 @@ import { useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import { styled } from 'styled-components'
-import { useEngine, useInversionFail } from '@/components/utils/EngineContext'
+// import { useEngine, useInversionFail } from '@/components/utils/EngineContext'
import { Message } from '@/design-system'
import { CloseButton } from '@/design-system/buttons'
import { Body } from '@/design-system/typography/paragraphs'
+import { usePromise } from '@/hooks/usePromise'
import { hideNotification } from '@/store/actions/actions'
import { RootState } from '@/store/reducers/rootReducer'
+import { isNotNull } from '@/utils'
+import {
+ useWorkerEngine,
+ WorkerEngine,
+} from '@/worker/socialWorkerEngineClient'
import { ExplicableRule } from './conversation/Explicable'
import { Appear } from './ui/animate'
@@ -26,44 +32,64 @@ type Notification = {
sévérité: 'avertissement' | 'information'
}
-function getNotifications(engine: Engine) {
- return Object.values(engine.getParsedRules())
- .filter(
- (rule) =>
- rule.rawNode.type === 'notification' &&
- !!engine.evaluate(rule.dottedName).nodeValue
+async function getNotifications(workerEngine: WorkerEngine) {
+ return (
+ await Promise.all(
+ Object.values(await workerEngine.asyncGetParsedRulesWithEngineId()).map(
+ async (rule) =>
+ rule.rawNode.type === 'notification' &&
+ !!(await workerEngine.asyncEvaluateWithEngineId(rule.dottedName))
+ .nodeValue
+ ? {
+ dottedName: rule.dottedName,
+ sévérité: rule.rawNode.sévérité,
+ résumé: rule.rawNode.résumé,
+ description: rule.rawNode.description,
+ }
+ : null
+ )
)
- .map(({ dottedName, rawNode: { sévérité, résumé, description } }) => ({
- dottedName,
- sévérité,
- résumé,
- description,
- }))
+ ).filter(isNotNull)
+ // .map(({ dottedName, rawNode: { sévérité, résumé, description } }) => ({
+ // dottedName,
+ // sévérité,
+ // résumé,
+ // description,
+ // }))
}
export default function Notifications() {
const { t } = useTranslation()
- const engine = useEngine()
- const inversionFail = useInversionFail()
+ const workerEngine = useWorkerEngine()
+ // const inversionFail = useInversionFail()
const hiddenNotifications = useSelector(
(state: RootState) => state.simulation?.hiddenNotifications
)
const dispatch = useDispatch()
- const messages: Array = (
- inversionFail
- ? [
- {
- dottedName: 'inversion fail',
- description: t(
- 'simulateurs.inversionFail',
- 'Le montant saisi abouti à un résultat impossible. Cela est dû à un effet de seuil dans le calcul des cotisations.\n\nNous vous invitons à réessayer en modifiant légèrement le montant renseigné (quelques euros de plus par exemple).'
- ),
- sévérité: 'avertissement',
- } as Notification,
- ]
- : (getNotifications(engine) as Array)
- ).filter(({ dottedName }) => !hiddenNotifications?.includes(dottedName))
+ const messages = usePromise(
+ async () =>
+ (await getNotifications(workerEngine)).filter(
+ ({ dottedName }) => !hiddenNotifications?.includes(dottedName)
+ ),
+ [hiddenNotifications, workerEngine],
+ []
+ )
+ // const messages: Array = (
+ // inversionFail
+ // ? [
+ // {
+ // dottedName: 'inversion fail',
+ // description: t(
+ // 'simulateurs.inversionFail',
+ // 'Le montant saisi abouti à un résultat impossible. Cela est dû à un effet de seuil dans le calcul des cotisations.\n\nNous vous invitons à réessayer en modifiant légèrement le montant renseigné (quelques euros de plus par exemple).'
+ // ),
+ // sévérité: 'avertissement',
+ // } as Notification,
+ // ]
+ // : (getNotifications(engine) as Array)
+ // )
+ // .filter(({ dottedName }) => !hiddenNotifications?.includes(dottedName))
const isMultiline = (str: string) => str.trim().split('\n').length > 1
diff --git a/site/source/components/Provider.tsx b/site/source/components/Provider.tsx
index c203ae58f..756bf2179 100644
--- a/site/source/components/Provider.tsx
+++ b/site/source/components/Provider.tsx
@@ -17,6 +17,7 @@ import { H1, H4 } from '@/design-system/typography/heading'
import { Link } from '@/design-system/typography/link'
import { Body, Intro } from '@/design-system/typography/paragraphs'
import { EmbededContextProvider } from '@/hooks/useIsEmbedded'
+import { WorkerEngineProvider } from '@/worker/socialWorkerEngineClient'
import { Message } from '../design-system'
import * as safeLocalStorage from '../storage/safeLocalStorage'
@@ -27,7 +28,7 @@ import { IframeResizer } from './IframeResizer'
import { ServiceWorker } from './ServiceWorker'
import { DarkModeProvider } from './utils/DarkModeContext'
-type SiteName = 'mon-entreprise' | 'infrance' | 'publicodes'
+type SiteName = 'mon-entreprise' | 'infrance'
export const SiteNameContext = createContext(null)
@@ -51,26 +52,28 @@ export default function Provider({
- (
- // eslint-disable-next-line react/jsx-props-no-spreading
-
- )}
- >
- {!import.meta.env.SSR &&
- import.meta.env.MODE === 'production' &&
- 'serviceWorker' in navigator && }
-
-
-
-
-
- {children}
-
-
-
-
-
+
+ (
+ // eslint-disable-next-line react/jsx-props-no-spreading
+
+ )}
+ >
+ {!import.meta.env.SSR &&
+ import.meta.env.MODE === 'production' &&
+ 'serviceWorker' in navigator && }
+
+
+
+
+
+ {children}
+
+
+
+
+
+
diff --git a/site/source/components/References.tsx b/site/source/components/References.tsx
index 0d616287d..8ad3a8861 100644
--- a/site/source/components/References.tsx
+++ b/site/source/components/References.tsx
@@ -3,11 +3,12 @@ import { utils } from 'publicodes'
import { useContext } from 'react'
import { styled } from 'styled-components'
-import { EngineContext, useEngine } from '@/components/utils/EngineContext'
+// import { useEngine } from '@/components/utils/EngineContext'
import { Grid } from '@/design-system/layout'
import { Link } from '@/design-system/typography/link'
import { Li, Ul } from '@/design-system/typography/list'
-import { capitalise0 } from '@/utils'
+import { usePromise } from '@/hooks/usePromise'
+import { capitalise0, isNotNullOrUndefined } from '@/utils'
export function References({
references,
@@ -131,21 +132,37 @@ const getDomain = (link: string) =>
)
export function RuleReferences({ dottedNames }: { dottedNames: DottedName[] }) {
- const engine = useContext(EngineContext)
+ const references = usePromise(
+ async () => {
+ const values = await Promise.all(
+ dottedNames.map(
+ async (dottedName) =>
+ (await asyncEvaluate(`${dottedName} != non`)).nodeValue
+ )
+ )
+
+ const refs = await Promise.all(
+ values
+ .filter(isNotNullOrUndefined)
+ .map(async (dottedName) =>
+ Object.entries(
+ (await asyncGetRule(dottedName as DottedName)).rawNode
+ .références ?? {}
+ )
+ )
+ )
+
+ return refs.flat()
+ },
+ [dottedNames],
+ []
+ )
return (
- {dottedNames
- .filter(
- (dottedName) => engine.evaluate(`${dottedName} != non`).nodeValue
- )
- .map((dottedName) =>
- Object.entries(
- engine.getRule(dottedName).rawNode.références ?? {}
- ).map(([title, href]) => (
-
- ))
- )}
+ {references.map(([title, href]) => (
+
+ ))}
)
}
diff --git a/site/source/components/RuleLink.tsx b/site/source/components/RuleLink.tsx
index 043ea4a67..ec02ccc97 100644
--- a/site/source/components/RuleLink.tsx
+++ b/site/source/components/RuleLink.tsx
@@ -1,12 +1,13 @@
import { DottedName } from 'modele-social'
-import Engine from 'publicodes'
import { RuleLink as EngineRuleLink } from 'publicodes-react'
-import React, { ReactNode, useContext } from 'react'
+import React, { ReactNode } from 'react'
import { Link } from '@/design-system/typography/link'
import { useSitePaths } from '@/sitePaths'
-
-import { EngineContext } from './utils/EngineContext'
+import {
+ usePromiseOnSituationChange,
+ useWorkerEngine,
+} from '@/worker/socialWorkerEngineClient'
// TODO : quicklink -> en cas de variations ou de somme avec un seul élément actif, faire un lien vers cet élément
export default function RuleLink(
@@ -16,23 +17,32 @@ export default function RuleLink(
children?: React.ReactNode
documentationPath?: string
linkComponent?: ReactNode
- engine?: Engine
+ engineId?: number
} & Omit, 'to' | 'children'>
) {
+ const engineId = props.engineId ?? 0
const { absoluteSitePaths } = useSitePaths()
- const defaultEngine = useContext(EngineContext)
+ const [loading, setLoading] = React.useState(true)
+ const [error, setError] = React.useState(false)
+ const workerEngine = useWorkerEngine()
- const engineUsed = props?.engine ?? defaultEngine
+ usePromiseOnSituationChange(() => {
+ setLoading(true)
+ setError(false)
- try {
- engineUsed.getRule(props.dottedName)
- } catch (error) {
- // eslint-disable-next-line no-console
- console.error(error)
+ return workerEngine
+ .asyncGetRuleWithEngineId(props.dottedName)
+ .catch(() => setError(true))
+ .then(() => setLoading(false))
+ }, [props.dottedName, workerEngine])
+ if (loading || error) {
return null
}
+ return <>EngineRuleLink>
+
+ // TODO : publicodes-react ne supporte pas encore les engines dans un worker
return (
`
${({ $bold }) => ($bold ? 'font-weight: bold;' : '')}
@@ -15,16 +17,18 @@ const Bold = styled.span<{ $bold: boolean }>`
export const SelectSimulationYear = () => {
const dispatch = useDispatch()
- const year = useContext(EngineContext).evaluate('date')
+ const workerEngine = useWorkerEngine()
+ const year = usePromiseOnSituationChange(
+ () => workerEngine.asyncEvaluateWithEngineId('date'),
+ [workerEngine]
+ )
const choices = [2022, 2023]
const actualYear = Number(
- (year.nodeValue?.toString().slice(-4) as Evaluation | undefined) ||
+ (year?.nodeValue?.toString().slice(-4) as Evaluation) ||
new Date().getFullYear()
)
- // return null // Waiting for next year.
-
return (
diff --git a/site/source/components/ShareSimulationBanner/index.tsx b/site/source/components/ShareSimulationBanner/index.tsx
index be0948218..e91dc1961 100644
--- a/site/source/components/ShareSimulationBanner/index.tsx
+++ b/site/source/components/ShareSimulationBanner/index.tsx
@@ -34,9 +34,9 @@ export function useUrl() {
? import.meta.env.VITE_FR_BASE_URL
: import.meta.env.VITE_EN_BASE_URL
- searchParams.set('utm_source', 'sharing')
+ searchParams?.set('utm_source', 'sharing')
- return siteUrl + path + '?' + searchParams.toString()
+ return siteUrl + path + '?' + (searchParams ?? '').toString()
}
const ButtonLabel = styled.span`
diff --git a/site/source/components/SimulateurWarning.tsx b/site/source/components/SimulateurWarning.tsx
index 490554ec4..878eda09b 100644
--- a/site/source/components/SimulateurWarning.tsx
+++ b/site/source/components/SimulateurWarning.tsx
@@ -1,5 +1,4 @@
import { Evaluation } from 'publicodes'
-import { useContext } from 'react'
import { Trans } from 'react-i18next'
import { styled } from 'styled-components'
@@ -8,8 +7,10 @@ import { Link } from '@/design-system/typography/link'
import { Li, Ul } from '@/design-system/typography/list'
import { Body } from '@/design-system/typography/paragraphs'
import { AbsoluteSitePaths } from '@/sitePaths'
-
-import { EngineContext } from './utils/EngineContext'
+import {
+ usePromiseOnSituationChange,
+ useWorkerEngine,
+} from '@/worker/socialWorkerEngineClient'
type SimulateurWarningProps = {
simulateur: Exclude
@@ -18,10 +19,13 @@ type SimulateurWarningProps = {
export default function SimulateurWarning({
simulateur,
}: SimulateurWarningProps) {
- const year = useContext(EngineContext)
- .evaluate('date')
- .nodeValue?.toString()
- .slice(-4) as Evaluation | undefined
+ const workerEngine = useWorkerEngine()
+ const year = usePromiseOnSituationChange(
+ () => workerEngine.asyncEvaluateWithEngineId('date'),
+ [workerEngine]
+ )
+ ?.nodeValue?.toString()
+ .slice(-4) as Evaluation
return (
+ workerEngine.asyncEvaluateWithEngineId({
+ value: dottedName,
+ arrondi: round ? 'oui' : 'non',
+ ...(!isTypeBoolean ? { unité: currentUnit } : {}),
+ }),
+ [workerEngine, dottedName, round, isTypeBoolean, currentUnit]
+ )
+ const rule = useAsyncGetRule(dottedName)
const initialRender = useInitialRender()
const [isFocused, setFocused] = useState(false)
const onChange = useCallback(
@@ -65,10 +73,11 @@ export function SimulationGoal({
},
[dispatch, onUpdateSituation, dottedName]
)
- if (evaluation.nodeValue === null) {
- return null
- }
- if (small && !editable && evaluation.nodeValue === undefined) {
+
+ if (
+ evaluation?.nodeValue === null ||
+ (small && !editable && evaluation?.nodeValue === undefined)
+ ) {
return null
}
@@ -96,7 +105,7 @@ export function SimulationGoal({
- {label || rule.title}
+ {label || rule?.title}
@@ -114,7 +123,7 @@ export function SimulationGoal({
)}
- {rule.rawNode.résumé && (
+ {rule?.rawNode.résumé && (
{editable ? (
- {!isFocused && !small && (
+ {!isFocused && !small && evaluation && (
)}
setFocused(true)}
onBlur={() => setFocused(false)}
onChange={onChange}
- missing={dottedName in evaluation.missingVariables}
+ missing={
+ evaluation && dottedName in evaluation.missingVariables
+ }
small={small}
formatOptions={{
maximumFractionDigits: round ? 0 : 2,
diff --git a/site/source/components/StackedBarChart.tsx b/site/source/components/StackedBarChart.tsx
index 0bb2d6a02..9e1e28d5c 100755
--- a/site/source/components/StackedBarChart.tsx
+++ b/site/source/components/StackedBarChart.tsx
@@ -9,9 +9,12 @@ import { styled } from 'styled-components'
import RuleLink from '@/components/RuleLink'
import useDisplayOnIntersecting from '@/components/utils/useDisplayOnIntersecting'
import { targetUnitSelector } from '@/store/selectors/simulationSelectors'
+import {
+ usePromiseOnSituationChange,
+ useWorkerEngine,
+} from '@/worker/socialWorkerEngineClient'
import { DisableAnimationContext } from './utils/DisableAnimationContext'
-import { useEngine } from './utils/EngineContext'
const BarStack = styled.div`
display: flex;
@@ -139,6 +142,7 @@ export function StackedBarChart({
const styles = useSpring({ opacity: displayChart ? 1 : 0 })
return !useContext(DisableAnimationContext) ? (
+ // @ts-ignore type too deep
@@ -201,20 +205,27 @@ export default function StackedRulesChart({
data,
precision = 0.1,
}: StackedRulesChartProps) {
- const engine = useEngine()
const targetUnit = useSelector(targetUnitSelector)
+ const workerEngine = useWorkerEngine()
- return (
- ({
- key: dottedName,
- value: engine.evaluate({ valeur: dottedName, unité: targetUnit })
- .nodeValue,
- legend: {title},
- title,
- color,
- }))}
- />
+ const datas = usePromiseOnSituationChange(
+ () =>
+ Promise.all(
+ data.map(async ({ dottedName, title, color }) => ({
+ key: dottedName,
+ value: (
+ await workerEngine.asyncEvaluateWithEngineId({
+ valeur: dottedName,
+ unité: targetUnit,
+ })
+ ).nodeValue,
+ legend: {title},
+ title,
+ color,
+ }))
+ ),
+ [data, targetUnit, workerEngine]
)
+
+ return
}
diff --git a/site/source/components/conversation/AnswerList.tsx b/site/source/components/conversation/AnswerList.tsx
index ff0a3de04..3a6445f42 100644
--- a/site/source/components/conversation/AnswerList.tsx
+++ b/site/source/components/conversation/AnswerList.tsx
@@ -1,11 +1,16 @@
import { DottedName } from 'modele-social'
-import { PublicodesExpression, RuleNode, utils } from 'publicodes'
+import {
+ EvaluatedNode,
+ PublicodesExpression,
+ RuleNode,
+ utils,
+} from 'publicodes'
import { useCallback, useMemo } from 'react'
import { Trans, useTranslation } from 'react-i18next'
import { useDispatch, useSelector } from 'react-redux'
import { styled } from 'styled-components'
-import { EvaluatedRule, useEngine } from '@/components/utils/EngineContext'
+import { EvaluatedRule } from '@/components/utils/EngineContext'
import { Message, PopoverWithTrigger } from '@/design-system'
import { Button } from '@/design-system/buttons'
import { Emoji } from '@/design-system/emoji'
@@ -17,6 +22,7 @@ import { Link } from '@/design-system/typography/link'
import { Body, Intro } from '@/design-system/typography/paragraphs'
import { useCurrentSimulatorData } from '@/hooks/useCurrentSimulatorData'
import { useNextQuestions } from '@/hooks/useNextQuestion'
+import { usePromise } from '@/hooks/usePromise'
import { answerQuestion, resetSimulation } from '@/store/actions/actions'
import { resetCompany } from '@/store/actions/companyActions'
import { isCompanyDottedName } from '@/store/reducers/companySituationReducer'
@@ -25,7 +31,7 @@ import {
companySituationSelector,
situationSelector,
} from '@/store/selectors/simulationSelectors'
-import { evaluateQuestion } from '@/utils'
+import { useWorkerEngine } from '@/worker/socialWorkerEngineClient'
import Value from '../EngineValue'
import { JeDonneMonAvis } from '../JeDonneMonAvis'
@@ -41,27 +47,43 @@ export default function AnswerList({ onClose, children }: AnswerListProps) {
const { t } = useTranslation()
const { currentSimulatorData } = useCurrentSimulatorData()
const dispatch = useDispatch()
- const engine = useEngine()
+ const workerEngine = useWorkerEngine()
const situation = useSelector(situationSelector)
const companySituation = useSelector(companySituationSelector)
const passedQuestions = useSelector(answeredQuestionsSelector)
- const answeredAndPassedQuestions = useMemo(
- () =>
- (Object.keys(situation) as DottedName[])
- .filter(
- (answered) => !passedQuestions.some((passed) => answered === passed)
+ const answeredAndPassedQuestions = usePromise(
+ async () =>
+ (
+ await Promise.all(
+ (Object.keys(situation) as DottedName[])
+ .filter(
+ (answered) =>
+ !passedQuestions.some((passed) => answered === passed)
+ )
+ .concat(passedQuestions)
+ .map(
+ async (dottedName) =>
+ await workerEngine.asyncGetRuleWithEngineId(dottedName)
+ )
)
- .concat(passedQuestions)
- .filter(
- (dottedName) =>
- engine.getRule(dottedName).rawNode.question !== undefined
- )
- .map((dottedName) => engine.getRule(dottedName)),
- [engine, passedQuestions, situation]
+ ).filter((rule) => rule.rawNode.question !== undefined),
+ [passedQuestions, situation, workerEngine],
+ [] as RuleNode[]
+ )
+ const nextQuestions = useNextQuestions()
+ const nextSteps = usePromise(
+ () =>
+ Promise.all(
+ nextQuestions.map(
+ async (dottedName) =>
+ workerEngine.asyncEvaluateWithEngineId(
+ await workerEngine.asyncGetRuleWithEngineId(dottedName)
+ ) as Promise
+ )
+ ),
+ [nextQuestions, workerEngine],
+ [] as EvaluatedRule[]
)
- const nextSteps = useNextQuestions().map((dottedName) =>
- engine.evaluate(engine.getRule(dottedName))
- ) as Array
const situationQuestions = useMemo(
() =>
@@ -70,23 +92,33 @@ export default function AnswerList({ onClose, children }: AnswerListProps) {
),
[answeredAndPassedQuestions]
)
- const companyQuestions = useMemo(
+ const companyQuestions = usePromise(
() =>
- Array.from(
- new Set(
- (
- [
- ...answeredAndPassedQuestions.map(({ dottedName }) => dottedName),
- ...Object.keys(situation),
- ...Object.keys(companySituation),
- ] as Array
- ).filter(isCompanyDottedName)
- )
- ).map((dottedName) => engine.getRule(dottedName)),
- [answeredAndPassedQuestions]
+ Promise.all(
+ Array.from(
+ new Set(
+ (
+ [
+ ...answeredAndPassedQuestions.map(
+ ({ dottedName }) => dottedName
+ ),
+ ...Object.keys(situation),
+ ...Object.keys(companySituation),
+ ] as Array
+ ).filter(isCompanyDottedName)
+ )
+ ).map((dottedName) => workerEngine.asyncGetRuleWithEngineId(dottedName))
+ ),
+ [answeredAndPassedQuestions, companySituation, situation, workerEngine],
+ [] as RuleNode[]
)
- const siret = engine.evaluate('établissement . SIRET').nodeValue as string
+ const siret = usePromise(
+ async () =>
+ (await workerEngine.asyncEvaluateWithEngineId('établissement . SIRET'))
+ .nodeValue as string,
+ [workerEngine]
+ )
return (
@@ -101,7 +133,7 @@ export default function AnswerList({ onClose, children }: AnswerListProps) {
Simulation en cours
-
+
{children}
-
+
@@ -231,7 +263,7 @@ export default function AnswerList({ onClose, children }: AnswerListProps) {
Prochaines questions
-
+
)}
@@ -259,7 +291,7 @@ function StepsTable({
title: rule.title,
})}
light
- dottedName={rule.dottedName}
+ dottedName={rule.dottedName as DottedName}
/>
@@ -273,14 +305,19 @@ function StepsTable({
function AnswerElement(rule: RuleNode) {
const dispatch = useDispatch()
- const engine = useEngine()
-
+ const workerEngine = useWorkerEngine()
const parentDottedName = utils.ruleParent(rule.dottedName) as DottedName
- const questionDottedName = rule.rawNode.question
- ? (rule.dottedName as DottedName)
- : parentDottedName && engine.getRule(parentDottedName).rawNode.API
- ? parentDottedName
- : undefined
+ const questionDottedName = usePromise(
+ async () =>
+ rule.rawNode.question
+ ? (rule.dottedName as DottedName)
+ : parentDottedName &&
+ (await workerEngine.asyncGetRuleWithEngineId(parentDottedName))
+ .rawNode.API
+ ? parentDottedName
+ : undefined,
+ [parentDottedName, rule.dottedName, rule.rawNode.question, workerEngine]
+ )
const handleChange = useCallback(
(value: PublicodesExpression | undefined) => {
@@ -311,7 +348,7 @@ function AnswerElement(rule: RuleNode) {
<>
-
+
)
diff --git a/site/source/pages/assistants/pour-mon-entreprise/index.tsx b/site/source/pages/assistants/pour-mon-entreprise/index.tsx
index 5aa7c5dc1..1dd4d1ddc 100644
--- a/site/source/pages/assistants/pour-mon-entreprise/index.tsx
+++ b/site/source/pages/assistants/pour-mon-entreprise/index.tsx
@@ -29,7 +29,6 @@ import { SimulateurCard } from '@/components/SimulateurCard'
import { FromTop } from '@/components/ui/animate'
import { ForceThemeProvider } from '@/components/utils/DarkModeContext'
import { useEngine } from '@/components/utils/EngineContext'
-import { Markdown } from '@/components/utils/markdown'
import { Message, Popover } from '@/design-system'
import { Button } from '@/design-system/buttons'
import { Container, Grid, Spacing } from '@/design-system/layout'
@@ -44,7 +43,6 @@ import { useSitePaths } from '@/sitePaths'
import { resetCompany } from '@/store/actions/companyActions'
import { SimulationConfig } from '@/store/reducers/rootReducer'
import { companySituationSelector } from '@/store/selectors/simulationSelectors'
-import { evaluateQuestion } from '@/utils'
import forms from './forms.svg'
import growth from './growth.svg'
@@ -294,9 +292,9 @@ const AskCompanyMissingDetails = () => {
{questions.map((question) => (
-
+ {/*
{evaluateQuestion(engine, question) ?? ''}
-
+ */}
import('@/public/data/ape-search.json'))
+ const lazyData = usePromise(() => import('@/public/data/ape-search.json'), [])
const lastIdxs = useRef>({})
const prevValue = useRef(searchQuery)
diff --git a/site/source/pages/simulateurs-et-assistants/metadata-src.ts b/site/source/pages/simulateurs-et-assistants/metadata-src.ts
index 74b7dbc2f..eb2cc2293 100644
--- a/site/source/pages/simulateurs-et-assistants/metadata-src.ts
+++ b/site/source/pages/simulateurs-et-assistants/metadata-src.ts
@@ -1,35 +1,36 @@
-import { ImmutableType } from '@/types/utils'
+import type { ImmutableType } from '@/types/utils'
-import { choixStatutJuridiqueConfig } from '../assistants/choix-du-statut/config'
-import { déclarationChargesSocialesIndépendantConfig } from '../assistants/declaration-charges-sociales-independant/config'
-import { demandeMobilitéConfig } from '../assistants/demande-mobilité/config'
-import { économieCollaborativeConfig } from '../assistants/économie-collaborative/config'
-import { pourMonEntrepriseConfig } from '../assistants/pour-mon-entreprise/config'
-import { rechercheCodeApeConfig } from '../assistants/recherche-code-ape/config'
+// import { choixStatutJuridiqueConfig } from '../assistants/choix-du-statut/config'
+// import { déclarationChargesSocialesIndépendantConfig } from '../assistants/declaration-charges-sociales-independant/config'
+// import { demandeMobilitéConfig } from '../assistants/demande-mobilité/config'
+// import { économieCollaborativeConfig } from '../assistants/économie-collaborative/config'
+// import { pourMonEntrepriseConfig } from '../assistants/pour-mon-entreprise/config'
+// import { rechercheCodeApeConfig } from '../assistants/recherche-code-ape/config'
import { PageConfig, SimulatorsDataParams } from '../simulateurs/_configs/types'
-import { artisteAuteurConfig } from '../simulateurs/artiste-auteur/config'
+// import { artisteAuteurConfig } from '../simulateurs/artiste-auteur/config'
import { autoEntrepreneurConfig } from '../simulateurs/auto-entrepreneur/config'
-import { auxiliaireMédicalConfig } from '../simulateurs/auxiliaire-médical/config'
-import { avocatConfig } from '../simulateurs/avocat/config'
-import { chirurgienDentisteConfig } from '../simulateurs/chirurgien-dentiste/config'
-import { chômagePartielConfig } from '../simulateurs/chômage-partiel/config'
-import { cipavConfig } from '../simulateurs/cipav/config'
-import { comparaisonStatutsConfig } from '../simulateurs/comparaison-statuts/config'
-import { coûtCréationEntrepriseConfig } from '../simulateurs/cout-creation-entreprise/config.js'
-import { dividendesConfig } from '../simulateurs/dividendes/config'
-import { eirlConfig } from '../simulateurs/eirl/config'
-import { entrepriseIndividuelleConfig } from '../simulateurs/entreprise-individuelle/config'
-import { eurlConfig } from '../simulateurs/eurl/config'
-import { expertComptableConfig } from '../simulateurs/expert-comptable/config'
-import { impôtSociétéConfig } from '../simulateurs/impot-societe/config'
-import { indépendantConfig } from '../simulateurs/indépendant/config'
-import { médecinConfig } from '../simulateurs/médecin/config'
-import { pamcConfig } from '../simulateurs/pamc/config'
-import { pharmacienConfig } from '../simulateurs/pharmacien/config'
-import { professionLibéraleConfig } from '../simulateurs/profession-libérale/config'
-import { sageFemmeConfig } from '../simulateurs/sage-femme/config'
-import { salariéConfig } from '../simulateurs/salarié/config'
-import { sasuConfig } from '../simulateurs/sasu/config'
+
+// import { auxiliaireMédicalConfig } from '../simulateurs/auxiliaire-médical/config'
+// import { avocatConfig } from '../simulateurs/avocat/config'
+// import { chirurgienDentisteConfig } from '../simulateurs/chirurgien-dentiste/config'
+// import { chômagePartielConfig } from '../simulateurs/chômage-partiel/config'
+// import { cipavConfig } from '../simulateurs/cipav/config'
+// import { comparaisonStatutsConfig } from '../simulateurs/comparaison-statuts/config'
+// import { coûtCréationEntrepriseConfig } from '../simulateurs/cout-creation-entreprise/config.js'
+// import { dividendesConfig } from '../simulateurs/dividendes/config'
+// import { eirlConfig } from '../simulateurs/eirl/config'
+// import { entrepriseIndividuelleConfig } from '../simulateurs/entreprise-individuelle/config'
+// import { eurlConfig } from '../simulateurs/eurl/config'
+// import { expertComptableConfig } from '../simulateurs/expert-comptable/config'
+// import { impôtSociétéConfig } from '../simulateurs/impot-societe/config'
+// import { indépendantConfig } from '../simulateurs/indépendant/config'
+// import { médecinConfig } from '../simulateurs/médecin/config'
+// import { pamcConfig } from '../simulateurs/pamc/config'
+// import { pharmacienConfig } from '../simulateurs/pharmacien/config'
+// import { professionLibéraleConfig } from '../simulateurs/profession-libérale/config'
+// import { sageFemmeConfig } from '../simulateurs/sage-femme/config'
+// import { salariéConfig } from '../simulateurs/salarié/config'
+// import { sasuConfig } from '../simulateurs/sasu/config'
/**
* Contient l'intégralité des données concernant les différents simulateurs et assistants
@@ -39,37 +40,37 @@ import { sasuConfig } from '../simulateurs/sasu/config'
const getMetadataSrc = (params: SimulatorsDataParams) => {
const data = {
// simulateurs:
- ...salariéConfig(params),
- ...entrepriseIndividuelleConfig(params),
- ...eirlConfig(params),
- ...sasuConfig(params),
- ...eurlConfig(params),
+ // ...salariéConfig(params),
+ // ...entrepriseIndividuelleConfig(params),
+ // ...eirlConfig(params),
+ // ...sasuConfig(params),
+ // ...eurlConfig(params),
...autoEntrepreneurConfig(params),
- ...indépendantConfig(params),
- ...artisteAuteurConfig(params),
- ...chômagePartielConfig(params),
- ...comparaisonStatutsConfig(params),
- ...économieCollaborativeConfig(params),
- ...pharmacienConfig(params),
- ...médecinConfig(params),
- ...chirurgienDentisteConfig(params),
- ...sageFemmeConfig(params),
- ...auxiliaireMédicalConfig(params),
- ...avocatConfig(params),
- ...expertComptableConfig(params),
- ...professionLibéraleConfig(params),
- ...pamcConfig(params),
- ...dividendesConfig(params),
- ...coûtCréationEntrepriseConfig(params),
- ...impôtSociétéConfig(params),
- ...cipavConfig(params),
+ // ...indépendantConfig(params),
+ // ...artisteAuteurConfig(params),
+ // ...chômagePartielConfig(params),
+ // ...comparaisonStatutsConfig(params),
+ // ...économieCollaborativeConfig(params),
+ // ...pharmacienConfig(params),
+ // ...médecinConfig(params),
+ // ...chirurgienDentisteConfig(params),
+ // ...sageFemmeConfig(params),
+ // ...auxiliaireMédicalConfig(params),
+ // ...avocatConfig(params),
+ // ...expertComptableConfig(params),
+ // ...professionLibéraleConfig(params),
+ // ...pamcConfig(params),
+ // ...dividendesConfig(params),
+ // ...coûtCréationEntrepriseConfig(params),
+ // ...impôtSociétéConfig(params),
+ // ...cipavConfig(params),
- // assistants:
- ...choixStatutJuridiqueConfig(params),
- ...déclarationChargesSocialesIndépendantConfig(params),
- ...demandeMobilitéConfig(params),
- ...pourMonEntrepriseConfig(params),
- ...rechercheCodeApeConfig(params),
+ // // assistants:
+ // ...choixStatutJuridiqueConfig(params),
+ // ...déclarationChargesSocialesIndépendantConfig(params),
+ // ...demandeMobilitéConfig(params),
+ // ...pourMonEntrepriseConfig(params),
+ // ...rechercheCodeApeConfig(params),
} as const
return data satisfies ImmutableType>
diff --git a/site/source/pages/simulateurs/NextSteps.tsx b/site/source/pages/simulateurs/NextSteps.tsx
index 996f458b9..d4eabe417 100644
--- a/site/source/pages/simulateurs/NextSteps.tsx
+++ b/site/source/pages/simulateurs/NextSteps.tsx
@@ -1,9 +1,6 @@
import { Trans, useTranslation } from 'react-i18next'
import { Condition, WhenAlreadyDefined } from '@/components/EngineValue'
-import { useEngine } from '@/components/utils/EngineContext'
-// import { Article } from '@/design-system/card'
-// import { Emoji } from '@/design-system/emoji'
import { Grid, Spacing } from '@/design-system/layout'
import { H2 } from '@/design-system/typography/heading'
import {
@@ -12,12 +9,16 @@ import {
} from '@/hooks/useCurrentSimulatorData'
import { GuideURSSAFCard } from '@/pages/simulateurs/cards/GuideURSSAFCard'
import { IframeIntegrationCard } from '@/pages/simulateurs/cards/IframeIntegrationCard'
-import { SimulatorRessourceCard } from '@/pages/simulateurs/cards/SimulatorRessourceCard'
import { useSitePaths } from '@/sitePaths'
+import {
+ usePromiseOnSituationChange,
+ useWorkerEngine,
+} from '@/worker/socialWorkerEngineClient'
import { AnnuaireEntreprises } from '../assistants/pour-mon-entreprise/AnnuaireEntreprises'
import { AutoEntrepreneurCard } from '../assistants/pour-mon-entreprise/AutoEntrepeneurCard'
import { CodeDuTravailNumeriqueCard } from '../assistants/pour-mon-entreprise/CodeDuTravailNumeriqueCard'
+import { SimulatorRessourceCard } from './cards/SimulatorRessourceCard'
interface NextStepsProps {
iframePath?: MergedSimulatorDataValues['iframePath']
@@ -27,11 +28,19 @@ interface NextStepsProps {
export function NextSteps({ iframePath, nextSteps }: NextStepsProps) {
const { absoluteSitePaths } = useSitePaths()
const { language } = useTranslation().i18n
- const engine = useEngine()
+ const workerEngine = useWorkerEngine()
const { key } = useCurrentSimulatorData()
- const guideUrssaf = guidesUrssaf.find(
- ({ associatedRule }) => engine.evaluate(associatedRule).nodeValue
+ const guideUrssaf = usePromiseOnSituationChange(
+ async () =>
+ (
+ await Promise.all(
+ guidesUrssaf.map(({ associatedRule }) =>
+ workerEngine.asyncEvaluateWithEngineId(associatedRule)
+ )
+ )
+ ).find(({ nodeValue }) => nodeValue),
+ [workerEngine]
)
if (!iframePath && !guideUrssaf) {
@@ -58,7 +67,7 @@ export function NextSteps({ iframePath, nextSteps }: NextStepsProps) {
{nextSteps &&
nextSteps.map((simulatorId) => (
-
+ {/* */}
))}
@@ -70,14 +79,14 @@ export function NextSteps({ iframePath, nextSteps }: NextStepsProps) {
/>
)}
- {key === 'salarié' && (
+ {/* {key === 'salarié' && (
- )}
+ )} */}
{guideUrssaf && language === 'fr' && (
-
+ {/* */}
)}
diff --git a/site/source/pages/simulateurs/_configs/types.ts b/site/source/pages/simulateurs/_configs/types.ts
index 542075dc9..d0c17d1a2 100644
--- a/site/source/pages/simulateurs/_configs/types.ts
+++ b/site/source/pages/simulateurs/_configs/types.ts
@@ -1,8 +1,8 @@
import type { TFunction } from 'i18next'
-import { DottedName } from 'modele-social'
-import { ASTNode, PublicodesExpression } from 'publicodes'
+import type { DottedName } from 'modele-social'
+import type { ASTNode, PublicodesExpression } from 'publicodes'
-import { AbsoluteSitePaths } from '@/sitePaths'
+import type { AbsoluteSitePaths } from '@/sitePaths'
export type Situation = Partial<
Record
diff --git a/site/source/pages/simulateurs/auto-entrepreneur/AutoEntrepreneur.tsx b/site/source/pages/simulateurs/auto-entrepreneur/AutoEntrepreneur.tsx
index 8575f9b3f..6db9947e1 100644
--- a/site/source/pages/simulateurs/auto-entrepreneur/AutoEntrepreneur.tsx
+++ b/site/source/pages/simulateurs/auto-entrepreneur/AutoEntrepreneur.tsx
@@ -19,8 +19,11 @@ import { H2 } from '@/design-system/typography/heading'
import { Body } from '@/design-system/typography/paragraphs'
export default function AutoEntrepreneur() {
+ console.log('ok')
+
return (
<>
+ aaaaaaaaaaaaaaa
}
afterQuestionsSlot={}
diff --git a/site/source/store/selectors/simulationSelectors.ts b/site/source/store/selectors/simulationSelectors.ts
index c086dd2af..123014f9c 100644
--- a/site/source/store/selectors/simulationSelectors.ts
+++ b/site/source/store/selectors/simulationSelectors.ts
@@ -1,10 +1,15 @@
import { DottedName } from 'modele-social'
-import Engine, { utils } from 'publicodes'
+import { utils } from 'publicodes'
import { useSelector } from 'react-redux'
import { createSelector } from 'reselect'
-import { useEngine } from '@/components/utils/EngineContext'
+import { usePromise } from '@/hooks/usePromise'
+// import { useEngine } from '@/components/utils/EngineContext'
import { RootState, Situation } from '@/store/reducers/rootReducer'
+import {
+ useWorkerEngine,
+ WorkerEngine,
+} from '@/worker/socialWorkerEngineClient'
export const configSelector = (state: RootState) =>
state.simulation?.config ?? {}
@@ -22,22 +27,34 @@ export const configObjectifsSelector = createSelector(
const emptySituation: Situation = {}
-export const useMissingVariables = ({
- engines,
-}: {
- engines: Array>
-}): Partial> => {
+export const useMissingVariables = (
+ workerEngines?: WorkerEngine[]
+): Partial> => {
const objectifs = useSelector(configObjectifsSelector)
+ const workerEngine = useWorkerEngine()
- return treatAPIMissingVariables(
- objectifs
- .flatMap((objectif) =>
- engines.map((e) => e.evaluate(objectif).missingVariables ?? {})
+ return usePromise(
+ async () => {
+ const evaluates = await Promise.all(
+ objectifs.flatMap((objectif) =>
+ (workerEngines ?? [workerEngine]).map(
+ async (e) =>
+ (await e.asyncEvaluateWithEngineId(objectif)).missingVariables ??
+ {}
+ )
+ )
)
- .reduce(mergeMissing, {}),
- useEngine()
+
+ return await treatAPIMissingVariables(
+ evaluates.reduce(mergeMissing, {}),
+ workerEngine
+ )
+ },
+ [objectifs, workerEngine, workerEngines],
+ {}
)
}
+
export const situationSelector = (state: RootState) =>
state.simulation?.situation ?? emptySituation
@@ -75,14 +92,26 @@ export const shouldFocusFieldSelector = (state: RootState) =>
*
* For instance, the commune field (API) will fill `commune . nom` `commune . taux versement transport`, `commune . département`, etc.
*/
-function treatAPIMissingVariables(
- missingVariables: Partial>,
- engine: Engine
-): Partial> {
- return (Object.entries(missingVariables) as Array<[Name, number]>).reduce(
- (missings, [name, value]: [Name, number]) => {
- const parentName = utils.ruleParent(name) as Name
- if (parentName && engine.getRule(parentName).rawNode.API) {
+async function treatAPIMissingVariables(
+ missingVariables: Partial>,
+ workerEngine: WorkerEngine
+): Promise>> {
+ return (
+ await Promise.all(
+ (Object.entries(missingVariables) as [DottedName, number][]).map(
+ async ([name, value]) => {
+ const parentName = utils.ruleParent(name) as DottedName
+ const rule =
+ parentName &&
+ (await workerEngine.asyncGetRuleWithEngineId(parentName))
+
+ return [name, value, parentName, rule.rawNode.API] as const
+ }
+ )
+ )
+ ).reduce(
+ (missings, [name, value, parentName, API]) => {
+ if (API) {
missings[parentName] = (missings[parentName] ?? 0) + value
return missings
@@ -91,9 +120,10 @@ function treatAPIMissingVariables(
return missings
},
- {} as Partial>
+ {} as Partial>
)
}
+
const mergeMissing = (
left: Record | undefined = {},
right: Record | undefined = {}
diff --git a/site/source/types/utils.d.ts b/site/source/types/utils.d.ts
index b6e6dbdc3..7cf0fdce4 100644
--- a/site/source/types/utils.d.ts
+++ b/site/source/types/utils.d.ts
@@ -13,6 +13,13 @@ type ImmutableIndex = Readonly<{
[K in keyof T]: ImmutableType
}>
+/**
+ * Mutable type
+ */
+export type Mutable = {
+ -readonly [K in keyof T]: Mutable
+}
+
/**
* Merge union of object
*
@@ -44,3 +51,11 @@ export type ToOptional = Partial>> &
type UndefinedProperties = {
[P in keyof T]-?: undefined extends T[P] ? P : never
}[keyof T]
+
+/**
+ * Replace the return type of a function
+ */
+export type ReplaceReturnType<
+ T extends (...a: never) => unknown,
+ TNewReturn,
+> = (...a: Parameters) => TNewReturn
diff --git a/site/source/utils/index.ts b/site/source/utils/index.ts
index 1f15606e2..9af3b9ab3 100644
--- a/site/source/utils/index.ts
+++ b/site/source/utils/index.ts
@@ -1,5 +1,5 @@
import { DottedName } from 'modele-social'
-import Engine, {
+import {
formatValue,
isPublicodesError,
PublicodesExpression,
@@ -234,21 +234,6 @@ export async function getIframeOffset(): Promise {
})
}
-export function evaluateQuestion(
- engine: Engine,
- rule: RuleNode
-): string | undefined {
- const question = rule.rawNode.question as Exclude<
- number,
- PublicodesExpression
- >
- if (question && typeof question === 'object') {
- return engine.evaluate(question as PublicodesExpression).nodeValue as string
- }
-
- return question
-}
-
export function buildSituationFromObject(
contextDottedName: Names,
situationObject: Record
diff --git a/site/source/worker/socialWorkerEngine.worker.ts b/site/source/worker/socialWorkerEngine.worker.ts
new file mode 100644
index 000000000..74841bd9a
--- /dev/null
+++ b/site/source/worker/socialWorkerEngine.worker.ts
@@ -0,0 +1,61 @@
+import rawRules, { DottedName } from 'modele-social'
+import Engine from 'publicodes'
+
+import type { ProviderProps } from '@/components/Provider'
+import i18n from '@/locales/i18n'
+import ruleTranslations from '@/locales/rules-en.yaml'
+import translateRules from '@/locales/translateRules'
+
+import { createWorkerEngine, WorkerEngineActions } from './workerEngine'
+
+function getUnitKey(unit: string): string {
+ const units = i18n.getResourceBundle('fr', 'units') as Record
+ const key = Object.entries(units)
+ .find(([, trans]) => trans === unit)?.[0]
+ .replace(/_plural$/, '')
+
+ return key || unit
+}
+
+let warnCount = 0
+let timeout: NodeJS.Timeout | null = null
+const logger = {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ warn: (message: string) => {
+ // console.warn(message)
+
+ warnCount++
+ timeout !== null && clearTimeout(timeout)
+ timeout = setTimeout(() => {
+ // eslint-disable-next-line no-console
+ console.warn('⚠️', warnCount, 'warnings in the engine')
+ warnCount = 0
+ }, 1000)
+ },
+ error: (message: string) => {
+ // eslint-disable-next-line no-console
+ console.error(message)
+ },
+ log: (message: string) => {
+ // eslint-disable-next-line no-console
+ console.log(message)
+ },
+}
+
+const init = ({ basename }: Pick) => {
+ let rules = rawRules
+ if (basename === 'infrance') {
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
+ rules = translateRules('en', ruleTranslations, rules)
+ }
+
+ const engine = new Engine(rules, { getUnitKey, logger })
+
+ return engine
+}
+
+export type Actions = WorkerEngineActions, DottedName>
+
+console.time('[createWorkerEngine]')
+createWorkerEngine(init)
+console.timeEnd('[createWorkerEngine]')
diff --git a/site/source/worker/socialWorkerEngineClient.tsx b/site/source/worker/socialWorkerEngineClient.tsx
new file mode 100644
index 000000000..95896a912
--- /dev/null
+++ b/site/source/worker/socialWorkerEngineClient.tsx
@@ -0,0 +1,329 @@
+import { DottedName } from 'modele-social'
+import {
+ createContext,
+ DependencyList,
+ useContext,
+ useEffect,
+ useMemo,
+ useState,
+ useTransition,
+} from 'react'
+
+import { ProviderProps } from '@/components/Provider'
+import { useSetupSafeSituation } from '@/components/utils/EngineContext'
+import { useLazyPromise, usePromise } from '@/hooks/usePromise'
+
+import { Actions } from './socialWorkerEngine.worker'
+import SocialeWorkerEngine from './socialWorkerEngine.worker?worker'
+import {
+ createWorkerEngineClient,
+ WorkerEngineClient,
+} from './workerEngineClient'
+
+export type WorkerEngine = NonNullable>
+
+// @ts-expect-error
+const WorkerEngineContext = createContext()
+
+// export const useWorkerEngineContext = () => {
+// const context = useContext(WorkerEngineContext)
+// if (!context) {
+// throw new Error(
+// 'You are trying to use the worker engine outside of its provider'
+// )
+// }
+
+// return context
+// }
+
+export const useWorkerEngine = () => {
+ const context = useContext(WorkerEngineContext)
+
+ if (!context) {
+ throw new Error(
+ 'You are trying to use the worker engine outside of its provider'
+ )
+ }
+
+ // if (!context) {
+ // throw new Error(
+ // 'You are trying to use the worker engine before it is ready'
+ // )
+ // }
+
+ return context
+}
+
+export const WorkerEngineProvider = ({
+ children,
+ basename,
+}: {
+ children: React.ReactNode
+ basename: ProviderProps['basename']
+}) => {
+ const workerEngine = useCreateWorkerEngine(basename)
+
+ useSetupSafeSituation(workerEngine)
+
+ if (workerEngine === undefined) {
+ return null
+ }
+
+ return (
+
+ {children}
+
+ )
+}
+
+// export type WorkerEngine = WorkerEngineClient
+// let workerClient: | null = null
+// setTimeout(() => {
+// const preparedWorker = new SocialeWorkerEngine()
+// const workerClient: WorkerEngineClient =
+// createWorkerEngineClient(
+// new SocialeWorkerEngine(),
+// () => {},
+// // (engineId) =>
+// // setSituationVersion((situationVersion) => {
+// // // console.log('??? setSituationVersion original')
+
+// // // situationVersion[engineId] =
+// // // typeof situationVersion[engineId] !== 'number'
+// // // ? 0
+// // // : situationVersion[engineId]++
+
+// // // return situationVersion
+// // return situationVersion + 1
+// // }),
+// { basename: 'mon-entreprise' }
+// )
+// workerClient.test.onSituationChange = function (engineId) {
+// console.log('original onSituationChange')
+// }
+// // }, 50)
+// console.time('loading')
+
+/**
+ * This hook is used to create a worker engine.
+ * @param basename
+ */
+export const useCreateWorkerEngine = (basename: ProviderProps['basename']) => {
+ const [situationVersion, setSituationVersion] = useState(0)
+ const [workerEngine, setWorkerEngine] =
+ useState>()
+ // console.log('llllllpppppppppppppppppppppppppp', workerClient)
+
+ const [transition, startTransition] = useTransition()
+
+ useEffect(() => {
+ // workerClient.test.onSituationChange = function (engineId) {
+ // console.log('transition...')
+
+ // startTransition(() => {
+ // setSituationVersion((situationVersion) => {
+ // // console.log('??? setSituationVersion original')
+
+ // // situationVersion[engineId] =
+ // // typeof situationVersion[engineId] !== 'number'
+ // // ? 0
+ // // : situationVersion[engineId]++
+
+ // // return situationVersion
+ // return situationVersion + 1
+ // })
+ // })
+ // }
+ const workerClient = createWorkerEngineClient(
+ new SocialeWorkerEngine(),
+ // () => {},
+ (engineId) =>
+ startTransition(() => {
+ setSituationVersion((situationVersion) => {
+ // console.log('??? setSituationVersion original')
+
+ // situationVersion[engineId] =
+ // typeof situationVersion[engineId] !== 'number'
+ // ? 0
+ // : situationVersion[engineId]++
+
+ // return situationVersion
+ return situationVersion + 1
+ })
+ }),
+ { basename: 'mon-entreprise' }
+ )
+
+ console.log('{init worker}', workerClient)
+ setWorkerEngine(workerClient)
+
+ void workerClient.asyncSetSituationWithEngineId({})
+ console.time('{init}')
+ let init = false
+ void workerClient.isWorkerReady.finally(() => {
+ init = true
+ console.timeEnd('{init}')
+ })
+
+ // example of usage
+ // void Promise.all([
+ // workerClient
+ // .asyncEvaluate('SMIC')
+ // .then((result) => console.log('{result}', result)),
+ // workerClient
+ // .asyncEvaluate('date')
+ // .then((result) => console.log('{result}', result)),
+ // ])
+
+ return () => {
+ !init && console.timeEnd('{init}')
+ console.log('{terminate worker}', workerClient)
+
+ // workerClient.terminate()
+ }
+ }, [basename])
+
+ // return workerEngine ? { ...workerEngine, situationVersion } : null
+ const memo = useMemo(
+ () => (workerEngine ? { ...workerEngine, situationVersion } : undefined),
+ [situationVersion, workerEngine]
+ )
+
+ return memo
+}
+
+/**
+ *
+ */
+// const useSituationVersion = (workerEngineCtx: WorkerEngineCtx) =>
+// workerEngineCtx.situationVersion
+// [engineId]
+
+interface Options {
+ defaultValue?: Default
+ workerEngine?: WorkerEngine
+}
+
+/**
+ * Wrapper around usePromise that adds the situation version to the dependencies
+ * @example const date = usePromiseOnSituationChange(() => asyncEvaluate('date'), []) // date will be updated when the situation changes
+ * @deprecated
+ */
+export const usePromiseOnSituationChange = (
+ promise: () => Promise,
+ deps: DependencyList,
+ { defaultValue, workerEngine: workerEngineOption }: Options = {}
+): T | Default => {
+ const defaultWorkerEngineCtx = useWorkerEngine()
+ const { situationVersion } = workerEngineOption ?? defaultWorkerEngineCtx
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ const state = usePromise(promise, [...deps, situationVersion], defaultValue)
+
+ return state
+}
+
+/**
+ * @deprecated
+ */
+export const useLazyPromiseOnSituationChange = (
+ promise: () => Promise,
+ deps: DependencyList,
+ { defaultValue, workerEngine: workerEngineOption }: Options = {}
+): [T | Default, () => Promise] => {
+ const defaultWorkerEngineCtx = useWorkerEngine()
+ const { situationVersion } = workerEngineOption ?? defaultWorkerEngineCtx
+
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ const tuple = useLazyPromise(
+ promise,
+ [...deps, situationVersion],
+ defaultValue
+ )
+
+ return tuple
+}
+
+/**
+ * This hook is used to get a rule in the worker.
+ * @param dottedName
+ * @param options
+ */
+export const useAsyncGetRule = <
+ // T extends unknown = undefined,
+ Default = undefined,
+>(
+ dottedName: DottedName,
+ { defaultValue, workerEngine: workerEngineOption }: Options = {}
+) => {
+ const defaultWorkerEngine = useWorkerEngine()
+ const workerEngine = workerEngineOption ?? defaultWorkerEngine
+
+ return usePromiseOnSituationChange(
+ async () => workerEngine.asyncGetRuleWithEngineId(dottedName),
+ [dottedName, workerEngine],
+ { defaultValue, workerEngine }
+ )
+}
+
+/**
+ * This hook is used to get parsed rules in the worker.
+ * @param engineId
+ */
+export const useAsyncParsedRules = <
+ Default = undefined, //
+>({
+ defaultValue,
+ workerEngine: workerEngineOption,
+}: Options = {}) => {
+ const defaultWorkerEngine = useWorkerEngine()
+ const workerEngine = workerEngineOption ?? defaultWorkerEngine
+
+ return usePromiseOnSituationChange(
+ async () => workerEngine.asyncGetParsedRulesWithEngineId(),
+ [workerEngine],
+ { defaultValue, workerEngine }
+ )
+}
+
+export const useShallowCopy = (
+ workerEngine: WorkerEngine
+): WorkerEngine | undefined => {
+ const [situationVersion, setSituationVersion] = useState(0)
+
+ // const defaultWorkerEngine = useWorkerEngine()
+ // const workerEngine = workerEngineParam
+ // ?? defaultWorkerEngine
+
+ // console.log('??? situ version', situationVersion)
+
+ const workerEngineCopy = usePromiseOnSituationChange(
+ async () => {
+ const copy = await workerEngine.asyncShallowCopyWithEngineId(() => {
+ // console.log('??? onSituationChange', copy)
+
+ setSituationVersion((x) => x + 1)
+ })
+
+ // copy.onSituationChange = (x) => {
+ // console.log('??? onSituationChange', copy)
+
+ // setSituationVersion(x)
+ // }
+
+ // console.log('??? xxxxxxxxxxxxxxxxxxxxxxxxxxx', copy)
+
+ return copy
+ },
+ [workerEngine],
+ { defaultValue: undefined, workerEngine }
+ )
+
+ const memo = useMemo(
+ () =>
+ workerEngineCopy ? { ...workerEngineCopy, situationVersion } : undefined,
+ [situationVersion, workerEngineCopy]
+ )
+
+ return memo
+}
diff --git a/site/source/worker/workerEngine.ts b/site/source/worker/workerEngine.ts
new file mode 100644
index 000000000..340183bf9
--- /dev/null
+++ b/site/source/worker/workerEngine.ts
@@ -0,0 +1,236 @@
+import Engine from 'publicodes'
+
+/**
+ * This file run any publicodes engine in a web worker.
+ */
+
+export type WorkerEngineActions<
+ InitParams extends unknown[],
+ Name extends string,
+> =
+ | {
+ action: 'init'
+ params: InitParams
+ result: number
+ }
+ | {
+ action: 'setSituation'
+ params: Parameters['setSituation']>
+ result: void
+ }
+ | {
+ action: 'evaluate'
+ params: Parameters['evaluate']>
+ result: ReturnType['evaluate']>
+ }
+ | {
+ action: 'getRule'
+ params: Parameters['getRule']>
+ result: ReturnType['getRule']>
+ }
+ | {
+ action: 'getParsedRules'
+ params: []
+ result: ReturnType['getParsedRules']>
+ }
+ | {
+ action: 'shallowCopy'
+ params: [] // no params cause we use engineId
+ result: number
+ }
+ | {
+ action: 'deleteShallowCopy'
+ params: [] // no params cause we use engineId
+ result: void
+ }
+
+type DistributiveOmit = T extends unknown
+ ? Omit
+ : never
+
+type GenericParams = {
+ /**
+ * The id of the engine to use, the default engine is 0
+ */
+ engineId?: number
+
+ /**
+ * The id of the message, used to identify the response
+ */
+ id: number
+}
+
+export type WorkerEngineAction<
+ Acts extends WorkerEngineActions,
+ T extends Acts['action'],
+> = Extract
+
+export const createWorkerEngine = <
+ Name extends string,
+ EngineType extends Engine,
+ InitParams extends unknown[] = unknown[],
+>(
+ init: (...params: InitParams) => EngineType
+) => {
+ type Params = DistributiveOmit<
+ WorkerEngineActions & GenericParams,
+ 'result'
+ >
+
+ let engines: (EngineType | undefined)[] = []
+ let queue: (Params & { engineId: number })[] = []
+
+ let setDefaultEngineReady: (() => void) | null = null
+ const isDefaultEngineReady = new Promise(
+ (resolve) => (setDefaultEngineReady = resolve as () => void)
+ )
+
+ const actions = (
+ data: Params
+ // & { engines: EngineType[] }
+ ) => {
+ const { engineId = 0, id, action, params } = data
+
+ const engine = engines[engineId]
+ if (!engine) {
+ throw new Error('Engine does not exist')
+ }
+
+ if (action === 'setSituation') {
+ // safeSetSituation(
+ // engine,
+ // ({ situation, }) => {
+ // console.error('setSituation', { situation, faultyDottedName })
+ // },
+ // ...params
+ // )
+ engine.setSituation(...params)
+
+ return { id }
+ } else if (action === 'evaluate') {
+ const result = engine.evaluate(...params)
+ console.log('[result]', result)
+
+ return { id, result }
+ } else if (action === 'getRule') {
+ const result = engine.getRule(...params)
+
+ return { id, result }
+ } else if (action === 'getParsedRules') {
+ const result = engine.getParsedRules()
+
+ return { id, result }
+ } else if (action === 'shallowCopy') {
+ engines.push(engine.shallowCopy() as EngineType)
+
+ return { id, result: engines.length - 1 }
+ } else if (action === 'deleteShallowCopy') {
+ if (engineId === 0) {
+ throw new Error('Cannot delete the default engine')
+ }
+
+ delete engines[engineId]
+ engines = engines.splice(engineId, 1)
+
+ console.log('[engines]', engines)
+
+ return { id }
+ } else {
+ console.log('[unknow message]', data)
+
+ return { id }
+ }
+ }
+
+ let timeout: NodeJS.Timeout | null = null
+ onmessage = async (e) => {
+ console.log('[onmessage]', e.data)
+
+ const { engineId = 0, id, action, params } = e.data as Params
+
+ try {
+ if (action === 'init') {
+ // console.log('[init engine]')
+ // const [{ basename }] = params
+ try {
+ // let rules = rawRules
+ // if (basename === 'infrance') {
+ // // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
+ // rules = translateRules('en', ruleTranslations, rules)
+ // }
+ // engineFactory(rules)
+
+ const engine = init(...params)
+
+ const engineId = engines.length
+ engines.push(engine)
+
+ postMessage({ id, result: engineId })
+ setDefaultEngineReady?.()
+ console.log('[engine ready]', engines[engineId])
+ } catch (e) {
+ console.error('[error]', e)
+ // postMessage('error')
+ }
+
+ return
+ }
+
+ await isDefaultEngineReady
+
+ queue.push({ engineId, id, action, params } as Params & {
+ engineId: number
+ })
+
+ if (timeout !== null) {
+ return
+ }
+
+ // timeout !== null && clearTimeout(timeout)
+ timeout = setTimeout(() => {
+ const aborts: number[] = []
+ const setSituationEncountered: boolean[] = []
+ const filteredQueue = [...queue]
+ .reverse()
+ .filter(({ action, engineId, id }) => {
+ if (action === 'setSituation')
+ setSituationEncountered[engineId] = true
+
+ const keep =
+ !setSituationEncountered[engineId] ||
+ (setSituationEncountered[engineId] && action !== 'evaluate')
+
+ if (!keep) aborts.push(id)
+
+ return keep
+ })
+ .reverse()
+ console.log('[start queue]', queue, filteredQueue)
+
+ console.time('bench')
+ postMessage({
+ batch: filteredQueue.map((params) => {
+ try {
+ const res = actions(params)
+
+ return res
+ } catch (error) {
+ return { id: params.id, error }
+ }
+ }),
+ })
+
+ console.timeEnd('bench')
+ const error = new Error(
+ 'aborts the action because the situation has changed'
+ )
+ postMessage({ batch: aborts.map((id) => ({ id, error })) })
+
+ queue = []
+ timeout = null
+ }, 50)
+ } catch (error) {
+ return postMessage({ id, error })
+ }
+ }
+}
diff --git a/site/source/worker/workerEngineClient.ts b/site/source/worker/workerEngineClient.ts
new file mode 100644
index 000000000..c41c11dc0
--- /dev/null
+++ b/site/source/worker/workerEngineClient.ts
@@ -0,0 +1,347 @@
+import type { WorkerEngineAction, WorkerEngineActions } from './workerEngine'
+
+if (import.meta.env.SSR || !window.Worker) {
+ throw new Error('Worker is not supported in this browser')
+}
+
+// const sleepMs = (ms: number) =>
+// new Promise((resolve) => setTimeout(resolve, ms))
+
+/**
+ * This file is a client to communicate with workerEngine.
+ */
+
+export type WorkerEngineClient<
+ Actions extends WorkerEngineActions,
+ InitParams extends unknown[] = unknown[],
+ Name extends string = string,
+> = ReturnType>
+
+export const createWorkerEngineClient = <
+ Actions extends WorkerEngineActions,
+ InitParams extends unknown[] = unknown[],
+ Name extends string = string,
+>(
+ worker: Worker,
+ onSituationChange: (engineId: number) => void = () => {},
+ ...initParams: WorkerEngineAction['params']
+) => {
+ type Action = WorkerEngineAction
+
+ const test = {
+ onSituationChange: (engineId: number) => {},
+ }
+
+ console.log('{createWorker}')
+
+ type WorkerEnginePromise = {
+ engineId: number
+ action: T
+ resolve: (value: unknown) => void
+ reject: (value: unknown) => void
+ }
+
+ let promises: WorkerEnginePromise[] = []
+ let lastCleanup: null | NodeJS.Timeout = null
+
+ const postMessage = async >(
+ engineId: number,
+ action: T,
+ ...params: U['params']
+ // ...params: U['params'] extends [] ? [] : U['params']
+ ) => {
+ console.log('{postMessage}', action, params)
+ const promiseTimeout = 100000
+ const warning = setTimeout(() => {
+ console.log('{promise waiting for too long, aborting!}', action, params)
+ promises[id].reject?.(new Error('timeout'))
+ }, promiseTimeout)
+
+ lastCleanup !== null && clearInterval(lastCleanup)
+ lastCleanup = setTimeout(() => {
+ if (promises.length) {
+ console.log('{cleanup}', promises.length)
+ promises = []
+ lastCleanup = null
+ }
+ }, 200000)
+
+ const id = promises.length
+ console.time(`execute-${id}`)
+
+ const stack = new Error().stack
+
+ const promise = new Promise((resolve, reject) => {
+ promises[id] = {
+ engineId,
+ action,
+ resolve: (...params: unknown[]) => {
+ clearTimeout(warning)
+
+ return resolve(...(params as Parameters))
+ },
+ reject: (err) => {
+ clearTimeout(warning)
+
+ console.error(err)
+ console.error(stack)
+ console.error(new Error((err as Error).message, { cause: stack }))
+
+ return reject(err)
+ },
+ }
+ })
+
+ worker.postMessage({ engineId, action, params, id })
+
+ return promise
+ }
+
+ worker.onmessageerror = function (e) {
+ console.log('{onmessageerror}', e)
+ }
+
+ worker.onerror = function (e) {
+ console.log('{onerror}', e)
+ }
+
+ const ppp = (data) => {
+ console.timeEnd(`execute-${data.id}`)
+ if (data.id === 0) {
+ console.timeEnd('loading')
+ }
+
+ if ('error' in data) {
+ return promises[data.id].reject?.(data.error)
+ }
+ promises[data.id].resolve?.(data.result)
+ }
+
+ worker.onmessage = function (e) {
+ console.log('{msg}', e.data)
+
+ if ('batch' in e.data) {
+ e.data.batch.forEach((data) => {
+ ppp(data)
+ })
+ } else {
+ ppp(e.data)
+ }
+ }
+
+ const engineId = 0
+ const isWorkerReady = postMessage(engineId, 'init', ...initParams)
+
+ const workerEngineConstruct = (
+ engineId: number,
+ onSituationChange: (engineId: number) => void = () => {}
+ ) => ({
+ test,
+ engineId,
+ worker,
+ isWorkerReady,
+ onSituationChange,
+ // promises,
+ postMessage,
+ terminate: () => {
+ workerEngine.worker.terminate()
+ promises.forEach((promise) => promise.reject?.('worker terminated'))
+ promises = []
+ },
+
+ // withEngineId: (engineId: number, promise: Promise) => {
+ // const tmp = workerEngine.engineId
+ // workerEngine.engineId = engineId
+
+ // promise()
+
+ // workerEngine.engineId = tmp
+ // },
+
+ // asynchronous setSituation function
+
+ /**
+ * This function is used to set the situation in the worker with a specific engineId.
+ */
+ asyncSetSituationWithEngineId: async (
+ // engineId: number,
+ ...params: Action<'setSituation'>['params']
+ ): Promise['result']> => {
+ // abort every action "evaluate"
+
+ console.log(')=>', promises)
+
+ // promises.forEach((promise) => {
+ // if (engineId === promise.engineId && promise.action === 'evaluate') {
+ // promise.reject?.('abort')
+ // }
+ // })
+
+ // if (!workerEngine.worker) {
+ // await sleepMs(10)
+
+ // return workerEngine.asyncSetSituationWithEngineId(engineId, ...params)
+ // }
+
+ // console.log('??? engideid', engineId, workerEngine, onSituationChange)
+
+ const ret = await workerEngine.postMessage(
+ engineId,
+ 'setSituation',
+ ...params
+ )
+
+ console.log('testtesttesttesttest', test)
+
+ test.onSituationChange(engineId)
+
+ return ret
+ },
+
+ /**
+ * This function is used to set the situation in the worker.
+ */
+ // asyncSetSituation: async (...params: Action<'setSituation'>['params']) =>
+ // workerEngine.asyncSetSituationWithEngineId(defaultEngineId, ...params),
+
+ // asynchronous evaluate function
+
+ /**
+ * This function is used to evaluate a publicodes expression in the worker with a specific engineId.
+ */
+ asyncEvaluateWithEngineId: async (
+ // engineId: number,
+ ...params: Action<'evaluate'>['params']
+ ): Promise['result']> => {
+ // if (!workerEngine.worker) {
+ // await sleepMs(10)
+
+ // return workerEngine.asyncEvaluateWithEngineId(engineId, ...params)
+ // }
+
+ const promise = await workerEngine.postMessage(
+ engineId,
+ 'evaluate',
+ ...params
+ )
+
+ // console.trace('{asyncEvaluateWithEngineId}')
+
+ return promise
+ },
+
+ /**
+ * This function is used to evaluate a publicodes expression in the worker.
+ */
+ // asyncEvaluate: async (...params: Action<'evaluate'>['params']) =>
+ // workerEngine.asyncEvaluateWithEngineId(defaultEngineId, ...params),
+
+ // asynchronous getRule function:
+
+ /**
+ * This function is used to get a publicodes rule that is in the worker with a specific EngineId.
+ */
+ asyncGetRuleWithEngineId: async (
+ // engineId: number,
+ ...params: Action<'getRule'>['params']
+ ): Promise['result']> => {
+ // if (!workerEngine.worker) {
+ // await sleepMs(10)
+
+ // return workerEngine.asyncGetRuleWithEngineId(engineId, ...params)
+ // }
+
+ return await workerEngine.postMessage(engineId, 'getRule', ...params)
+ },
+
+ /**
+ * This function is used to get a rule in the worker.
+ */
+ // asyncGetRule: async (...params: Action<'getRule'>['params']) =>
+ // workerEngine.asyncGetRuleWithEngineId(defaultEngineId, ...params),
+
+ // asynchronous getParsedRules function
+
+ /**
+ * This function is used to get all the parsed rules in the worker with a specific engineId.
+ */
+ asyncGetParsedRulesWithEngineId: async () // engineId: number
+ : Promise['result']> => {
+ // if (!workerEngine.worker) {
+ // await sleepMs(10)
+
+ // return workerEngine.asyncGetParsedRulesWithEngineId(engineId)
+ // }
+
+ return await workerEngine.postMessage(engineId, 'getParsedRules')
+ },
+
+ /**
+ * This function is used to get all the parsed rules in the worker.
+ */
+ // asyncGetParsedRules: async () =>
+ // workerEngine.asyncGetParsedRulesWithEngineId(defaultEngineId),
+
+ // asynchronous shallowCopy function
+
+ /**
+ * This function is used to shallow copy an engine in the worker with a specific engineId.
+ */
+ asyncShallowCopyWithEngineId: async (
+ onSituationChange: () => void = () => {}
+ ) => {
+ // engineId: number
+ // if (!workerEngine.worker) {
+ // await sleepMs(10)
+
+ // return workerEngine.asyncShallowCopyWithEngineId(engineId)
+ // }
+
+ const newEngineId = await workerEngine.postMessage(
+ engineId,
+ 'shallowCopy'
+ )
+
+ // return {
+ // ...workerEngine,
+ // engineId: newEngineId,
+ // }
+
+ // const uu = Object.assign({}, workerEngine)
+ // uu.engineId = newEngineId
+
+ // console.log('???[newEngineId]', newEngineId, engineId)
+
+ return workerEngineConstruct(newEngineId, onSituationChange)
+ },
+
+ /**
+ * This function is used to shallow copy an engine in the worker.
+ */
+ // asyncShallowCopy: async () => ({
+ // ...context,
+ // engineId: await workerEngine.asyncShallowCopyWithEngineId(
+ // defaultEngineId
+ // ),
+ // }),
+
+ // asynchronous deleteShallowCopy function
+
+ /**
+ * * This function is used to delete a shallow copy of an engine in the worker.
+ */
+ asyncDeleteShallowCopy: async () // engineId: number
+ : Promise['result']> => {
+ // if (!workerEngine.worker) {
+ // await sleepMs(10)
+
+ // return workerEngine.asyncDeleteShallowCopy(engineId)
+ // }
+
+ return await workerEngine.postMessage(engineId, 'deleteShallowCopy')
+ },
+ })
+ const workerEngine = workerEngineConstruct(engineId, onSituationChange)
+
+ return workerEngine
+}
diff --git a/site/source/workerEngine.tsx b/site/source/workerEngine.tsx
deleted file mode 100644
index 4a4a4f9b6..000000000
--- a/site/source/workerEngine.tsx
+++ /dev/null
@@ -1,350 +0,0 @@
-import { DottedName } from 'modele-social'
-import React, { DependencyList, useContext, useEffect, useState } from 'react'
-
-import { ProviderProps } from './components/Provider'
-import type { Action, Actions } from './engine.worker'
-import EngineWorker from './engine.worker?worker'
-import { usePromise } from './hooks/usePromise'
-
-if (!window.Worker) {
- throw new Error('Worker is not supported in this browser')
-}
-
-interface WorkerEngine {
- worker: Worker
- postMessage: >(
- engineId: number,
- action: T,
- ...params: U['params']
- ) => Promise
- isWorkerReady: Promise
- promises: {
- resolve: (value: unknown) => void
- reject: (value: unknown) => void
- }[]
-}
-
-const initWorkerEngine = (
- basename: ProviderProps['basename'],
- setSituationVersion: (updater: (situationVersion: number) => number) => void
-) => {
- const newWorker: Worker = new EngineWorker()
-
- const promises: WorkerEngine['promises'] = []
-
- const postMessage = async >(
- engineId: number,
- action: T,
- ...params: U['params']
- ) => {
- if (action === 'setSituation') {
- setSituationVersion((situationVersion) => situationVersion + 1)
- }
-
- const warning = setTimeout(() => {
- console.log('promise waiting for too long, aborting!', action, params)
- promises[id].reject?.(new Error('timeout'))
- }, 5000)
-
- const id = promises.length
- const promise = new Promise((resolve, reject) => {
- promises[id] = {
- resolve(...params: unknown[]) {
- clearTimeout(warning)
-
- return resolve(...(params as Parameters))
- },
- reject(err) {
- clearTimeout(warning)
-
- return reject(err)
- },
- }
- })
-
- newWorker.postMessage({ engineId, action, params, id })
-
- return promise
- }
-
- newWorker.onmessage = function (e) {
- console.log('msg:', e.data)
- if ('error' in e.data) {
- return promises[e.data.id].reject?.(e.data.error)
- }
- promises[e.data.id].resolve?.(e.data.result)
- }
-
- const isWorkerReady = postMessage(0, 'init', { basename })
-
- const workerEngine = {
- worker: newWorker,
- postMessage,
- isWorkerReady,
- promises,
- }
-
- return workerEngine
-}
-
-const sleepMs = (ms: number) =>
- new Promise((resolve) => setTimeout(resolve, ms))
-
-let worker: ReturnType | null = null
-
-/**
- * This hook is used to create a worker engine.
- * @param basename
- */
-const useCreateWorkerEngine = (basename: ProviderProps['basename']) => {
- const [situationVersion, setSituationVersion] = useState(0)
-
- useEffect(() => {
- worker = initWorkerEngine(basename, setSituationVersion)
-
- console.time('init')
- void worker.isWorkerReady.then(() => console.timeEnd('init'))
-
- // example of usage
- void Promise.all([
- asyncEvaluate('SMIC').then((result) => console.log('result', result)),
- asyncEvaluate('date').then((result) => console.log('result', result)),
- ])
-
- return () => {
- console.log('worker terminated!')
-
- worker?.worker.terminate()
- worker?.promises.forEach((promise) =>
- promise.reject?.('worker terminated')
- )
- worker = null
- }
- }, [basename])
-
- return situationVersion
-}
-
-// asynchronous setSituation function
-
-/**
- * This function is used to set the situation in the worker with a specific engineId.
- */
-export const asyncSetSituationWithEngineId = async (
- engineId: number,
- ...params: Action<'setSituation'>['params']
-): Promise['result']> => {
- if (!worker) {
- await sleepMs(10)
-
- return asyncSetSituationWithEngineId(engineId, ...params)
- }
-
- return await worker.postMessage(engineId, 'setSituation', ...params)
-}
-
-/**
- * * This function is used to set the situation in the worker.
- */
-export const asyncSetSituation = async (
- ...params: Action<'setSituation'>['params']
-) => asyncSetSituationWithEngineId(0, ...params)
-
-// asynchronous evaluate function
-
-/**
- * This function is used to evaluate a publicodes expression in the worker with a specific engineId.
- */
-export const asyncEvaluateWithEngineId = async (
- engineId: number,
- ...params: Action<'evaluate'>['params']
-): Promise['result']> => {
- if (!worker) {
- await sleepMs(10)
-
- return asyncEvaluateWithEngineId(engineId, ...params)
- }
-
- return await worker.postMessage(engineId, 'evaluate', ...params)
-}
-
-/**
- * This function is used to evaluate a publicodes expression in the worker.
- */
-export const asyncEvaluate = async (...params: Action<'evaluate'>['params']) =>
- asyncEvaluateWithEngineId(0, ...params)
-
-/**
- * This hook is used to evaluate a publicodes expression in the worker.
- * @param defaultValue
- */
-// export const useAsyncEvaluate = (
-// defaultValue?: T
-// ) => {
-// const [response, setResponse] = useState['result'] | T>(
-// defaultValue as T
-// )
-
-// const evaluate = useCallback(async (value: PublicodesExpression) => {
-// const result = await asyncEvaluate(value)
-// setResponse(result)
-
-// return result
-// }, [])
-
-// return [response, evaluate] as const
-// }
-
-// asynchronous getRule function:
-
-/**
- * This function is used to get a publicodes rule that is in the worker with a specific EngineId.
- */
-export const asyncGetRuleWithEngineId = async (
- engineId: number,
- ...params: Action<'getRule'>['params']
-): Promise['result']> => {
- if (!worker) {
- await sleepMs(10)
-
- return asyncGetRuleWithEngineId(engineId, ...params)
- }
-
- return await worker.postMessage(engineId, 'getRule', ...params)
-}
-
-/**
- * This function is used to get a rule in the worker.
- */
-export const asyncGetRule = async (...params: Action<'getRule'>['params']) =>
- asyncGetRuleWithEngineId(0, ...params)
-
-/**
- * This hook is used to get a rule in the worker.
- * @param defaultValue
- */
-export const useAsyncGetRule = <
- Names extends DottedName,
- // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-constraint
- T extends unknown = undefined
->(
- dottedName: Names,
- defaultValue?: T
-) =>
- usePromiseOnSituationChange(
- () => asyncGetRule(dottedName),
- [dottedName],
- defaultValue
- )
-
-// asynchronous getParsedRules function
-
-/**
- * This function is used to get all the parsed rules in the worker with a specific engineId.
- */
-export const asyncGetParsedRulesWithEngineId = async (
- engineId: number
-): Promise['result']> => {
- if (!worker) {
- await sleepMs(10)
-
- return asyncGetParsedRulesWithEngineId(engineId)
- }
-
- return await worker.postMessage(engineId, 'getParsedRules')
-}
-
-/**
- * This function is used to get all the parsed rules in the worker.
- */
-export const asyncGetParsedRules = async () =>
- asyncGetParsedRulesWithEngineId(0)
-
-/**
- *
- */
-export const useParsedRules = (engineId = 0) =>
- usePromiseOnSituationChange(
- async () => await asyncGetParsedRulesWithEngineId(engineId),
- [engineId]
- )
-
-// asynchronous shallowCopy function
-
-/**
- * This function is used to shallow copy an engine in the worker with a specific engineId.
- */
-export const asyncShallowCopyWithEngineId = async (
- engineId: number
-): Promise['result']> => {
- if (!worker) {
- await sleepMs(10)
-
- return asyncShallowCopyWithEngineId(engineId)
- }
-
- return await worker.postMessage(engineId, 'shallowCopy')
-}
-
-/**
- * This function is used to shallow copy an engine in the worker.
- */
-export const asyncShallowCopy = async () => asyncShallowCopyWithEngineId(0)
-
-// asynchronous deleteShallowCopy function
-
-/**
- * * This function is used to delete a shallow copy of an engine in the worker.
- */
-export const asyncDeleteShallowCopy = async (
- engineId: number
-): Promise['result']> => {
- if (!worker) {
- await sleepMs(10)
-
- return asyncDeleteShallowCopy(engineId)
- }
-
- return await worker.postMessage(0, 'deleteShallowCopy', { engineId })
-}
-
-const SituationUpdated = React.createContext(0)
-
-export const SituationUpdatedProvider = ({
- children,
- basename,
-}: {
- children: React.ReactNode
- basename: ProviderProps['basename']
-}) => {
- const situationVersion = useCreateWorkerEngine(basename)
-
- return (
-
- {children}
-
- )
-}
-
-export const useSituationUpdated = () => {
- const situationVersion = useContext(SituationUpdated)
-
- return situationVersion
-}
-
-/**
- * Wrapper around usePromise that adds the situation version to the dependencies
- * @example const date = usePromiseOnSituationChange(() => asyncEvaluate('date'), []) // date will be updated when the situation changes
- */
-export const usePromiseOnSituationChange = (
- promise: () => Promise,
- deps: DependencyList,
- defaultValue?: Default
-): T | Default => {
- const situationVersion = useSituationUpdated()
-
- // eslint-disable-next-line react-hooks/exhaustive-deps
- const state = usePromise(promise, [...deps, situationVersion], defaultValue)
-
- return state
-}
diff --git a/site/vite.config.ts b/site/vite.config.ts
index cc126c08a..9fd88b5df 100644
--- a/site/vite.config.ts
+++ b/site/vite.config.ts
@@ -41,6 +41,17 @@ export default defineConfig(({ command, mode }) => ({
IS_STAGING: mode === 'production' && !isProductionBranch(mode),
IS_PRODUCTION: mode === 'production' && isProductionBranch(mode),
},
+ worker: {
+ plugins: [
+ yaml({
+ transform(data, filePath) {
+ return filePath.endsWith('/rules-en.yaml')
+ ? cleanAutomaticTag(data)
+ : data
+ },
+ }),
+ ],
+ },
plugins: [
{
name: 'run-script-on-file-change',